[SevillaDotNet] Mesa redonda de .NET 6

Tras la .NET Conf 2021 y el lanzamiento de .NET 6 tenemos una enorme cantidad de novedades: C# 10, .NET 6, Visual Studio 2022, etc.

¿Algo mejor que montar una mesa redonda donde invitar a diferentes desarrolladores y repasar las novedades principales?

La fecha

Será el próximo Jueves, 25 de Noviembre de 18:30h a 20:00h (GMT+1).

¿Te apuntas?

Puedes seguir el evento en YouTube:

Más información

Más novedades de .NET 6, PriorityQueue

Diría que es hasta sorprendente tras ver durante años diferentes implementaciones de PriorityQueue incluidas algunas usadas interamente en Frameworks de Microsoft, que nunca ha existido algo público directamente expuesto en .NET. Esto ha sido así hasta la llegada de .NET 6. En este artículo vamos a conocer las posibilidades de PriorityQueue.

Queue

Para explicar las novedades introducidas vamos a crear una pequeña clase que nos permita trabajar con personas.

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Vamos a comenzar trabajando con Queue. Se trata de una estructura de datos genérica en la que los elementos se recuperan en el orden en que se añaden.

var persons = new Queue();

persons.Enqueue(new Person() { Name = "Javier" });
persons.Enqueue(new Person() { Name = "David" });
persons.Enqueue(new Person() { Name = "Pedro" });
persons.Enqueue(new Person() { Name = "Jesus" });

Vamos a utilizar el método TryDequeue que quita el elemento situado al principio y lo copia en el parámetro de salida.

while (persons.TryDequeue(out var person))
{
    Console.WriteLine($"{person.Name}");
}

La salida será algo como:

  • Javier
  • David
  • Pedro
  • Jesus

Justo lo que esperábamos y ya conocíamos, elementos ordenados obteniendo en cada momento el primero del principio de la colección.

Ahora imagina que quieres utilizar otro criterio para priorizar el orden en el que aparecen los elementos, independientemente del orden en que se pongan en cola.

Digamos que en nuestro ejemplo, cada persona tiene una edad y queremos ordenar usando ese criterio.

PriorityQueue

Podemos usar la nueva clase PriorityQueue con ese objetivo, usar otro criterio para priorizar.

Añadimos varias personas a un PriorityQueue:

var prioritizedAgents = new PriorityQueue();

prioritizedAgents.Enqueue(new Person() { Name = "Javier" }, 36);
prioritizedAgents.Enqueue(new Person() { Name = "David" }, 45);
prioritizedAgents.Enqueue(new Person() { Name = "Pedro" }, 41); 
prioritizedAgents.Enqueue(new Person() { Name = "Jesus" }, 33);

Como podemos ver en el ejemplo anterior, se especifica el tipo a añadir en la cola y un segundo tipo, que se usarará para la priorización.

En nuestro caso, tenemos una cola de personas y la prioridad es la edad donde se usa un int.

while (prioritizedAgents.TryDequeue(out var person, out var age))
{
    Console.WriteLine($"{person.Name}, age {age}");
}

¿Cuál es el resultado?

  • Jesus, age 33
  • Javier, age 36
  • Pedro, age 41
  • David, age 45

Las personas ordenadas por edad. Hay que tener en cuenta el orden relativo a como se añadieron a la cola. Al priorizar,un valor más bajo otorga una prioridad más alta.

Por supuesto, el tipo utilizado para definir la prioridad no tiene que ser un número; puede ser cualquier cosa que implemente IComparer.

Estamos ante una novedad excelente para problemas en los que las estructuras de datos se procesan según el orden y además otro parámetro que otorga prioridad.

Más información

[.NET 6] DateOnly y TimeOnly

Estamos muy cerca del lanzamiento de .NET, el próximo 9 de Noviembre. Así que, es un momento ideal para comenzar una nueva seria de artículos donde hablar de novedades. En este artículo, vamos a hablar de DateOnly y TimeOnly.

Clock

Trabajando con fechas

Hasta ahora, para trabajar con fechas usábamos la clase DateTime.

var date = new DateTime(1985, 7, 23);
var onlyDate = DateTime.Now.Date;

Con bastantes constructores y métodos, podíamos crear fechas de diferentes formas pero siempre trabajando con el tipo DateTime que incluye el tiempo aunque no lo necesitásemos.

De igual forma, no tenemos una forma de representar una hora del día sin incluir información de la fecha. Tenemos a nuestra disposición la clase TimesSpan que pueden representar tiempo transcurrido (2 horas, 20 minutos y 5 segundos) pero para representar una hora del día necesitamos DateTime.

Nuevas posibilidades

Con la llegada de .NET 6 se introducen nuevas estructuras, DateOnly y TimeOnly que permiten representar fechas sin una hora del día y una hora del día sin fecha respectivamente.

// DateOnly is a new struct in .NET 6 that represents just a date.
DateOnly july23th = new DateOnly(1985, 7, 23);

// TimeOnly is a new type in .NET 6. Stores a time without a date.
TimeOnly tenThirtyPM = new TimeOnly(22, 30); // 22:30, or 10:30 PM

DateOnly

La nueva struct cuenta con los mismo métodos que ya existían en DateTime para modificar la fecha: AddDays, AddMonths y AddYears.

// Like with DateTime, we can add days, months, and years.
var dateOnlyFromDateTime = dateOnlyFromDateTime.AddYears(1).AddMonths(2).AddDays(15); // Apr 29th, 2001

DateOnly almacena los valores como un entero, donde el valor 0 corresponde al 0 de Enero, 0001. De esta forma, es posible convertir un entero a un DateOnly usando el método FromDayNumber.

DateOnly dateOnlyInteger = new(1985, 7, 23); // Jul 23th 1985
int dayNumber = dateOnlyInteger.DayNumber;
DateOnly resultFromDayNumber = DateOnly.FromDayNumber(dayNumber);

También es posible creat una instancia de DateOnly desde una cadena usando el método TryParse.

// We can parse like uusing DateTime with the TryParse method.
DateOnly.TryParse("01/21/2020", out DateOnly result);

TimeOnly

La struct TimeOnly almacena la información usando un long que representa el número de ticks desde medianoche.

TimeOnly elevenTen = new TimeOnly(11, 10);
long ticks = elevenTen.Ticks;
TimeOnly timeOnlyTicks = new TimeOnly(ticks);

Podemos crear una instancia TimeOnly desde un DateTime usando el método FromDateTime.

DateTime dateTime = new DateTime(2020, 12, 12, 8, 00, 00);
TimeOnly timeOnlyFromDateTime = TimeOnly.FromDateTime(dateTime);

Podemos realizar operaciones matemáticas entre instancias de TimeOnly que nos devolverá como resultado un TimeSpan.

var afternoon = new TimeOnly(17, 00); // 5:00 PM
var morning = new TimeOnly(8, 00); // 8:00 AN
TimeSpan difference = afternoon - morning; // 9 hours

En general, nuevas structs que en mi opinión se integrarán facilmente con el resto de posibilidades de .NET y sobretodo será útil en tareas donde haya operaciones de serialización o bases de datos.

¿Qué te parecen estas novedades?. Recuerda, puedes usar los comentarios de la entrada para dejar tu opinión.

Más Información

.NET Blog: Date, Time, and Time Zone Enhancements in .NET 6

.NET MAUI Preview 3

Con la Preview 3 de NET MAUI llegan más novedades como:

  • Añadidos más cambios relacionados con HostBuilder, clase Startup.
  • Nueva API para gestionar el ciclo de vida de la aplicación.
  • Añadidos mas controles (DatePicker, TimePicker, SearchBar, Stepper, etc).
  • Añadidos mas cambios en Layouts.
  • Añadidas nuevas APIs de accesibilidad.
  • Primeros cambios añadiendo soporte para Windows usando WinUI 3.

En este artículo, vamos a hacer un repaso a todo lo que incluye la Preview 3, además de repasar que nos esperar en próximas Previews.

.NET MAUI Preview 3

Startup

Las aplicaciones .NET MAUI van a utilizar una clase Startup que permitirá:

  • Incluye un método Configure para canalizar los procesos de registro de servicios, registro de handlers o personalización de la aplicación.
  • Poder crear un HostBuilder personalizado.

Por ejemplo:

public void Configure(IAppHostBuilder appBuilder)
{
    appBuilder = appBuilder
        .UseCompatibilityRenderers()
        .UseMauiApp<MyApp>();
}

Por defecto, si no quiere personalizar nada especial, o bien, quieres utilizar tu propio contenedor de dependencias, etc., podrás hacerlo.

Ciclo de vida

El ciclo de vida de una aplicación es sumamente importante. Hay muchas acciones que se deben realizar cuando la aplicación pasa a segundo plano, o bien cuando regresa de suspensión. Por lo tanto, tener un control detallado de cada paso es importante.

En .NET MAUI hay una nueva API para el ciclo de vida con el firme objetivo de cubrir todas las peticiones recibidas en Xamarin.Forms y mejorar las áreas donde las posibilidades necesitaban ser expandidas.

Por supuesto, al igual que en Xamarin.Forms podrás sobrecargar diferentes métodos para saber cuándo la aplicación pasa a segundo plano etc. Igualmente, se añaden mucho más control en el ciclo de vida de otros elementos como ventanas o Views.

Por otro lado, también se va a permitir conectar directamente con eventos nativos de cada plataforma. Veamos un ejemplo:

appBuilder
    .ConfigureLifecycleEvents(events =>
    {
        events.AddEvent<Action<string>>("CustomEventName", value => LogEvent("CustomEventName"));

#if __ANDROID__
        events.AddAndroid(android => android
            .OnActivityResult((a, b, c, d) => LogEvent(nameof(AndroidLifecycle.OnActivityResult), b.ToString()))
            .OnBackPressed((a) => LogEvent(nameof(AndroidLifecycle.OnBackPressed)))
            .OnConfigurationChanged((a, b) => LogEvent(nameof(AndroidLifecycle.OnConfigurationChanged)))
            .OnCreate((a, b) => LogEvent(nameof(AndroidLifecycle.OnCreate)))
            .OnDestroy((a) => LogEvent(nameof(AndroidLifecycle.OnDestroy)))
            .OnNewIntent((a, b) => LogEvent(nameof(AndroidLifecycle.OnNewIntent)))
            .OnPause((a) => LogEvent(nameof(AndroidLifecycle.OnPause)))
            .OnPostCreate((a, b) => LogEvent(nameof(AndroidLifecycle.OnPostCreate)))
            .OnPostResume((a) => LogEvent(nameof(AndroidLifecycle.OnPostResume)))
            .OnPressingBack((a) => LogEvent(nameof(AndroidLifecycle.OnPressingBack)) && false)
            .OnRequestPermissionsResult((a, b, c, d) => LogEvent(nameof(AndroidLifecycle.OnRequestPermissionsResult)))
            .OnRestart((a) => LogEvent(nameof(AndroidLifecycle.OnRestart)))
            .OnRestoreInstanceState((a, b) => LogEvent(nameof(AndroidLifecycle.OnRestoreInstanceState)))
            .OnResume((a) => LogEvent(nameof(AndroidLifecycle.OnResume)))
            .OnSaveInstanceState((a, b) => LogEvent(nameof(AndroidLifecycle.OnSaveInstanceState)))
            .OnStart((a) => LogEvent(nameof(AndroidLifecycle.OnStart)))
            .OnStop((a) => LogEvent(nameof(AndroidLifecycle.OnStop))));

        // Add some cool features/things
        var shouldPreventBack = 1;
        events.AddAndroid(android => android
            .OnResume(a =>
            {
                LogEvent(nameof(AndroidLifecycle.OnResume), "shortcut");
            })
            .OnPressingBack(a =>
            {
                LogEvent(nameof(AndroidLifecycle.OnPressingBack), "shortcut");

                return shouldPreventBack-- > 0;
            })
            .OnBackPressed(a => LogEvent(nameof(AndroidLifecycle.OnBackPressed), "shortcut"))
            .OnRestoreInstanceState((a, b) =>
            {
                LogEvent(nameof(AndroidLifecycle.OnRestoreInstanceState), "shortcut");

                Debug.WriteLine($"{b.GetString("test2", "fail")} == {b.GetBoolean("test", false)}");
            })
            .OnSaveInstanceState((a, b) =>
            {
                LogEvent(nameof(AndroidLifecycle.OnSaveInstanceState), "shortcut");

                b.PutBoolean("test", true);
                b.PutString("test2", "yay");
            }));
    }

Como puedes ver, recibimos información de cada evento nativo de una aplicación nativa Android. Si plugins se enlazan de esta forma con estos eventos, se notificará tanto al plugin como a tu propia subscripción. Esto hará que el uso de plugins dónde se requiere un control de ciclo de vida sea más sencillo sin necesidad de añadir código de inicialización, etc.

¿Qué te parece?

Novedades en accesibilidad

Añadir más control y mejorar la API de accesibilidad para evitar confusiones además de alinear el comportamiento en todos los casos y plataformas es una de las prioridades en unos de los apartados con tanta importancia como es la accesibilidad.

<Entry
    Text="Entry text TH"
    FontSize="14"
    SemanticProperties.Description="Description text"
    SemanticProperties.Hint="Hint text"/>

Se añade el concepto de SemanticProperties. Hablamos de una serie de propiedades que añaden información extra a Views para permitir interpretar correctamente que ocurre cuando se utiliza el lector de pantalla, o navegación por teclado.

Habrá más novedades en próximas Previews. Puedes ver mas información en la Spec.

Soporte a Windows

Con la llegada de la versión 0.5 de Project Reunion, incluimos soporte a Windows en .NET MAUI con WinUI 3:

.NET MAUI en Windows
.NET MAUI en Windows!

Hasta aquí el pequeño repaso a algunas de las novedades en la Preview 3 de .NET MAUI. En la próxima Preview se esperan mas controles, novedades a nivel de UI, Microsoft Graphics y mucho más!. Por supuesto, estaremos aquí en el blog repasando con detallada cada novedad.

Más información

.NET MAUI Check tool

.NET MAUI es un framework de desarrollo de aplicaciones multiplataforma disponible en .NET 6. Con las primeras Previews ya disponibles, es necesario descargar e instalar varios requisitos desde el propio .NET 6 a los workloads para Android, iOS o Catalyst.

¿Y si pudieras utilizar una herramienta que prepare todo el entorno e instale todo los requisitos automáticamente?.

.NET MAUI Check tool

Se trata de una herramienta de línea de comandos que valida los requisitos necesarios para poder lanzar aplicaciones .NET MAUI y permite automáticamente descargar e instalar los requisitos pendientes.

Para instalar la herramienta basta con abrir la linea de comandos y ejecutar el siguiente comando:

dotnet tool install -g Redth.Net.Maui.Check

Para lanzar la herramienta basta con ejecutar el siguiente comando:

maui-check

Otras opciones

La herramienta utiliza un archivo de manifiesto para obtener la última versión de todos los requisitos. Por defecto, se utiliza un archivo de manifiesto disponible en https://aka.ms/dotnet-maui-check-manifest, pero si quieres utilizar un manifiesto personalizado es posible:

maui-check --manifest /some/other/file

Si se utiliza la herramienta en CI, es posible ejecutarla evitando cualquier confirmación usando el argumento –non-interactive.

maui-check --non-interactive

Más información