El Proyecto Tye


Proyecto Tye  es una herramienta experimental que nos permitirá desarrollar, probar, y desplegar micro servicios y aplicaciones distribuidas de una forma simple.  Si es que decidiste seguir ese diseño arquitectónico. 

Este post esta basado en el artículo original de: https://devblogs.microsoft.com/aspnet/introducing-project-tye/ para que no tengan excusas con el ingles y con el plus de joedayz :P. 


Es claro que al crear múltiples proyectos creamos un sitio web, su backend API y diferentes servicios que se comunicaran entre ellos. Lo que busca esta herramienta es hacer muchas de las tareas repetitivas simples e incluso publicar tu aplicación en una plataforma como Kubernetes. 

Los objetivos del proyecto son:

1. Permitir el dearrollo de microservicios simple, ¿cómo?:
    • Ejecutando varios servicios con un comando
    • Usando dependencias en contenedores
    • Descubrir la dirección de otros servicios usando convenciones simples

2. Automatizar el despliegue de aplicaciones .NET a Kubernetes, ¿cómo?:
    • Automaticamente se contenerizan las aplicaciones .NET
    • Generar manifiestos de Kubernetes con poco conocimiento del mismo o configuración
    • Usar un simple  archivo de configuración.

Microsoft ha demostrado el uso de Tye en dos sesiones del Microsoft Build, los cuales son: Cloud Native Apps with .NET and AKS y Journey to one .NET.

Instalación

El pre-requisito es tener instalado .NET Core 3.1 en nuestra computadora. 

Luego instalar Tye como una herramienta global usando este comando:

dotnet tool update -g Microsoft.Tye --version "0.3.0-alpha.20319.3"
NOTA: Verifica si hay una versión más reciente en https://github.com/dotnet/tye/blob/master/docs/getting_started.md


Ejecutando un simple servicio

1. Crear un directorio llamado microservicios e ingresar en el:

mkdir microservicios
cd microservicios

2. Luego crea un proyecto frontend:

dotnet new razor -n frontend



3. Ahora ejecutemos el programa usando tye run:

tye run frontend




Tye como puede ver, ha construido, esta ejecutando y monitoreando la aplicación frontend. 

Tye viene con un dashboard para ver el estado de tu aplicación. Vamos a http://localhost:8000 para ver este. 




El fin del dashboard es mostrar todos nuestros servicios. La columna Bindings tiene los links URLs del servicio. La columna Logs nos permite ver los logs del servicio.  



Los servicios escritos con ASP.NET Core usan puertos asignados de forma aleatoria, si es que estos no son configurados de forma explicita. Esto es por defecto y útil para evitar conflictos de puertos. 

Ejecutando múltiples servicios

Ahora imaginemos el escenario de una multi-aplicación donde nuestro frontend se comunica con un proyecto backend. Para continuar para el tye run con Ctrl + C

1. Crea un backend API que el frontend llamará internamente en el directorio microservicios

dotnet new webapi -n backend


2. Crea un archivo solución y agrega estos dos proyectos:

dotnet new sln
dotnet sln add frontend backend


Tendremos una solución llamada microservicios.sln que hace referencia a los dos proyectos.



3. Ahora ejecutamos tye run para correr la solución.

tye run


Ahora el dashboard mostrará los servicios frontend y el backend. 



Si vamos al servicio backend este retornará un error HTTP 404 para su URL root. 



Comunicando el frontend con el backend

Para lograr que ambas aplicaciones se comuniquen, tye usa service discovery. Tye usa variables de entorno para especificar los strings de conexión y URIs de servicios. 

Para usar el service discovery de Tye, se usa Microsoft.Extensions.Configuration que esta disponible en ASP.NET Core o en el proyecto .NET Core Worker. Pero, tambien se dispone de Microsoft.Tye.Extensions.Configuration y algunas extensiones específicas de Tye. Más información de como Tye implementa service discovery lo puede ver este link.

1. Primero, asegurate de parar el comando tye run con Ctrl +  C. Y abramos la solución en un editor de texto.

2. Agrega el archivo WeatherForecast.cs en el proyecto frontend


using System;
namespace frontend
 {
   public class WeatherForecast
   {
     public DateTime Date { get; set; }
     public int TemperatureC { get; set; }
     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
     public string Summary { get; set; }
   }
 }



Este archivo corresponde con el backend WeatherForecast.cs que se crea por defecto en un webapi template. 


3. Agregamos un WeatherClient.cs al proyecto frontend con el siguiente contenido:


using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
namespace frontend
{
   public class WeatherClient
   {
      private readonly JsonSerializerOptions options = new JsonSerializerOptions()
      {
         PropertyNameCaseInsensitive = true,
         PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
      };
      private readonly HttpClient client;
      public WeatherClient(HttpClient client)
      {
         this.client = client;
      }
      public async Task<WeatherForecast[]> GetWeatherAsync()
      {
         var responseMessage = await this.client.GetAsync("/weatherforecast");
         var stream = await responseMessage.Content.ReadAsStreamAsync();
         return await JsonSerializer.DeserializeAsync<WeatherForecast[]>(stream, options);
      }
   }
}


4. Agregamos el package Microsoft.Tye.Extensions.Configuration al proyecto frontend


dotnet add frontend/frontend.csproj package Microsoft.Tye.Extensions.Configuration --version "0.2.0-*"

5. Ahora registremos este cliente en el frontend agregando lo siguiente al método ConfigureServices presente en el archivo Startup.cs:


        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            /** Add the following to wire the client to the backend **/
            services.AddHttpClient<WeatherClient>(client =>
            {
                client.BaseAddress = Configuration.GetServiceUri("backend");
            });
            /** End added code **/
        }



Esto conectará el WeatherClient para usar la URL correcta para el servicio de back-end.

6. Agrega una propiedad Forecasts en la página Index ubicada en Pages\Index.cshtml.cs en el proyecto frontend

public WeatherForecast[] Forecasts { get; set; }




7. Cambiar el método OnGet para usar el WeatherClient y llamar al servicio backend y guardar el resultado en la propiedad Forecasts:

public async Task OnGet([FromServices]WeatherClient client)
 {
   Forecasts = await client.GetWeatherAsync();
 }


8. Cambiaremos la vista Index.cshtml para renderizar la propiedad Forecasts en la página razor:

@page
@model IndexModel
@{
   ViewData["Title"] = "Home page";
}
<div class="text-center">
   <h1 class="display-4">Welcome</h1>
   <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
Weather Forecast:
<table class="table">
   <thead>
      <tr>
         <th>Date</th>
         <th>Temp. (C)</th>
         <th>Temp. (F)</th>
         <th>Summary</th>
      </tr>
   </thead>
   <tbody>
      @foreach (var forecast in @Model.Forecasts)
      {
         <tr>
            <td>@forecast.Date.ToShortDateString()</td>
            <td>@forecast.TemperatureC</td>
            <td>@forecast.TemperatureF</td>
            <td>@forecast.Summary</td>
         </tr>
      }
   </tbody>
</table>


9. Ejecutar el proyecto con tye run y por fin, el servicio frontend podra llamar al servicio backend




Esquema de Configuración de Tye

Tye tiene un archivo de configuración denominado tye.yaml para permitir configuración personalizada. 
Este archivo contiene todos tus proyectos y dependencias externas. Si se tiene una solución existente, Tye automaticamente populará este con todos sus proyectos actuales. 

Para inicializar este archivo, se necesita ejecutar este comando en el directorio microservicios para generar un archivo tye.yaml:

tye init


El contenido del archivo tye.yaml luce así:


El alcance a nivel global (como el nombre de nodo) es donde la configuración global es aplicada. 

Tye.yaml lista todos los servicios de la aplicación bajo el nodo services. Este es el lugar para configuración por servicio.

Para aprender, a cerca de, la especificación yaml y esquema, tu puedes encontrar más información en este link



Agregando dependencias externas (Redis)

Tye hace simple el agregar dependencias externas a nuestras aplicaciones. Veremos ahora como agregar redis a el frontend y backend para guardar datos. 

Tye puede usar Docker para ejecutar imagenes que que son parte de nuestra aplicación. Hay que asegurarnos de que Docker esta instalado en nuestra máquina. 

1. Hay que cambiar el método WeatherForecastController.Get() en el proyecto backend para poner en cache la información del clima en redis usando un IDistributedCache

2. Agregar los siguientes usings en el archivo:

using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;

3. Modificar el método Get():

[HttpGet]
public async Task<string> Get([FromServices]IDistributedCache cache)
{
   var weather = await cache.GetStringAsync("weather");
   if (weather == null)
   {
      var rng = new Random();
      var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
      {
         Date = DateTime.Now.AddDays(index),
         TemperatureC = rng.Next(-20, 55),
         Summary = Summaries[rng.Next(Summaries.Length)]
      })
      .ToArray();
      weather = JsonSerializer.Serialize(forecasts);
      await cache.SetStringAsync("weather", weather, new DistributedCacheEntryOptions
      {
         AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5)
      });
   }
   return weather;
}



Esto guardará la data de clima en Redis con un tiempo de expiración de 5 segundos. 

4. Agregar la referencia Microsoft.Extensions.Caching.StackExchangeRedis en el proyecto backend:

cd backend/
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
cd ..


5. Modificamos el Startup.ConfigureServices en el proyecto backend para agregar la implementación IDistributedCache de redis. 

public void ConfigureServices(IServiceCollection services)
  {
    services.AddControllers();
    services.AddStackExchangeRedisCache(o =>
    {
      o.Configuration = Configuration.GetConnectionString("redis");
     });
   }

Esto configura redis en la configuración string para el servicio redis inyectado por el tye host. 


6. Modifica el tye.yaml para incluir redis como dependencia.


name: microservice
services:
- name: backend
  project: backend\backend.csproj
- name: frontend
  project: frontend\frontend.csproj
- name: redis
  image: redis
  bindings:
  - port: 6379
    connectionString: "${host}:${port}"
- name: redis-cli
  image: redis
  args: "redis-cli -h redis MONITOR"


Se ha agregado 2 servicios a el archivo tye.yaml. El servicio redis y el servicio redis-cli que usaremos para ver los datos enviados y recuperados desde redis. 

NOTA: El formato "${host}:${port}" en la propiedad connectionString substituíra los valores del host y port para producir un connection string que pueden ser usados con el StackExchange.Redis.


7. Ubicate en el directorio microservicios y ejecuta tye run


Ve a la aplicación frontend y verifica que los datos retornados son los mismos despues de refrescar la página varias veces. Nuevo contenido será cargado cada 5 segundos, de modo que si esperas un buen tiempo y refrescas, veras nuevos datos. También puedes ver los logs del redis-cli  usando el dashboard y ver la data que esta siendo cacheada en redis. 






Desplegando a Kubernetes

Tye hace que el proceso de desplegar tu aplicación a Kubernetes sea muy simple con conocimiento mínimo o configuración requerida.

NOTA: Tye usa tus credenciales actuales para pushear tus imagenes Docker y acceder a clusters de Kubernetes. Si tu ya has configurado kubectl apuntando a un contexto, esto es lo que tye deploy va a usar. 



NOTA: Aquí puedes ver a que contextos de kubernetes te has conectado anteriormente y si ves un check, ahí estarás apuntando actualmente. 

Antes de Desplegar tu aplicación, asegurate de tener lo siguiente:

1. Docker instalado. 
2. Un container registry. Docker por defecto crea un container registry en Dockerhub. También podemos usar Azure Container Registry (ACR) u otro container registry de tu elección. 
3. Un cluster de Kubernetes. Hay diferentes opciones aquí, cómo:
NOTA: Para un container registry provisto por otro cloud provider (diferente a Dockerhub), tendrás que seguir las instrucciones provistas por dicho proveedor. 


Creando Grupo de Recursos, Container Registry y AKS Cluster

Partimos de que no tenemos grupo de recursos, container registry, ni cluster AKS. Así que definimos primero las variables de entorno respectivas:

jose@Azure:~$ REGION_NAME=eastus
jose@Azure:~$ RESOURCE_GROUP=tyeworkshop
jose@Azure:~$ ACR_NAME=acr$RANDOM
jose@Azure:~$ AKS_CLUSTER_NAME=akstyecluster

Lo creamos con el siguiente comando: 

# Creamos el grupo de recursos

az group create \
 --name $RESOURCE_GROUP \
 --location $REGION_NAME

# Creamos el ACR
az acr create \
 --resource-group $RESOURCE_GROUP \
 --location $REGION_NAME \
 --name $ACR_NAME \
 --sku basic

acr28602.azurecr.io 

# Creamos el AKS cluster con ACR integration

az aks create -n $AKS_CLUSTER_NAME -g $RESOURCE_GROUP --generate-ssh-keys --attach-acr $ACR_NAME


Asociando mi Kubectl a mi AKS

Según lo expuesto anteriormente me conecto a mi AKS con el siguiente comando:

az aks get-credentials --resource-group $RESOURCE_GROUP  --name $AKS_CLUSTER_NAME


Y me conecto a mi ACR:
az acr login -n acr28602

Desplegando Redis

tye deploy no desplegará la configuración de redis, así que se necesitará desplegar esto primero, ejecutando:

kubectl apply -f https://raw.githubusercontent.com/dotnet/tye/master/docs/tutorials/hello-tye/redis.yaml

Esto creará un deployment y un service para redis. 


kubectl get all






Tye deploy

Podemos desplegar tu aplicación ejecutando el siguiente comando:

tye deploy --interactive

Se te consultará para que ingreses tu container registry. Esto es necesario para taggear imagenes, y pushear ellas a una ubicación accesible por Kubernetes.  Ese dato ya lo tenemos.





Se nos consultará también por la conexión string para redis: el valor correcto es redis:6379





tye deploy creará un Kubernetes secret para guardar el connection string. 

Tye usa Kubernetes secrets para guardar información de conexión de dependencias como redis, ya que estas pueden vivir fuera del cluster. Tye automaticamente generará mappings entre nombres de servicios, nombres de binding y nombres de secretos. 

tye deploy realiza varios pasos para desplegar una aplicación en Kubenetes. Este hará lo siguiente:

  • Creará una imagen docker para cada proyecto en tu aplicación.
  • Pusheara la imagen docker en tu container registry
  • Generará un Kubernetes deployment y service para cada proyecto
  • Aplicará el Deployment y Service generado en tu actual contexto de Kubernetes



Cuando ya tenemos este resultado, deberíamos tener 3 pods ejecutandose despues del despliegue.



Podemos visitar la aplicación frontend, para eso se necesita port-forward para acceder al frontend desde fuera del cluster.

kubectl port-forward svc/frontend 5000:80

Ahora ve a http://localhost:5000 para ver la aplicación frontend trabajando en kubernetes





La comunicación es sobre HTTP no HTTPS. Habilitar TLS es una opción por defecto en el futuro. 

Agregar un registry en tye.yaml

Si deseas usar tye deploy como parte de un sistema CI/CD, se espera que ya tengas tu archivo tye.yaml inicializado. Tu necesitas agregar un container registry a tye.yaml. Entonces agregaremos al tye.yaml:

registry: acr28602.azurecr.io

Ahora es posible usar tye deploy sin --interactive puesto que ya el registry esta como parte de la configuración.  Mas información sobre tye deploy en este link.



Desinstalando tu aplicación


Después de desplegar y jugar un rato con la aplicación, ya estamos listos para eliminar todos los recursos asociados con el cluster de Kubernetes. Para lograr eso ejecutamos:

tye undeploy

Esto eliminará todos los recursos desplegados. Si deseas ver que recursos serán eliminados, puedes ejecutar:

tye undeploy --what-if



NOTA: Esto solo elimina la aplicación. Queda aún disponible el AKS y ACR. Te recomiendo luego eliminar el grupo de recursos para que no incurras en facturación o se acabe el saldo de tu trial.

Continúa tu aprendizaje


Si deseas experimentar mas con Tye, se tiene una gran variedad de ejemplos y tutoriales que puedes verificar en:


Roadmap de Tye


A continuación algunas integraciones que han sido liberadas recientemente. Existe también información provista de coo comenzar con cada uno de ellos:

Tye seguirá en fase experimental y con .NET 5 cuando se lance, se evaluará que se tiene en ese momento para ver que hacer con él en el futuro.

El objetivo del team es sacar cada mes nuevas capacidades como:

  • Más destinos de despliegue
  • Soporte a Sidecar
  • Desarrollo conectado
  • Migraciones de base de datos

Conclusión


Tye hace el desarrollo de aplicaciones distribuidas sencillo. Pruebalo, da tu feedback, crea issues y participa en Github

Post Original de: Amiee Lo (Program Manager, ASP .NET)
Traducción y pruebas en AKS:  Joe

Vídeo para practicarlo en casa:






Enjoy!!

Joe










Share:

ReactJS con Spring Boot - Parte 1


He comenzado a desarrollar material de REACTJS con Spring Boot para poder trabajar con esta gran librería. 

Me ha encantado, porque ya me había acostumbrado a Angular (En otro post libere un curso de Angular), pero, los invito a revisar las dos primeras clases. Espero sus comentarios.

Iniciando con ReactJS



Componentes ReactJS y Material UI



Compartelo con tus amigos y no te olvides de visitar nuestro canal de youtube.




Enjoy!

Jose
Share:

¿Qué característica de C# le gustaría en Java?


C# es un lenguaje que ha avanzado mucho. Haremos un resumen de las mejoras (sólo algunas) de  este lenguaje desde la versión 6.0 a la 9.0  y te invitaremos a pensar cuales le vendrían genial tenerlas en Java.



C# 6

Propiedades de sólo Lectura

Para tener propiedades de sólo lectura, esto se consigue eliminando el setter, cómo se muestra a continuación:
public DateTime FechaIngreso { get; }
Interpolación en String

Esta característica nos haría mas fácil y rápido el combinar strings y variables. Sólo hay que usar
$ al inicio del string. De esta manera sólo escribimos directamente una variable al interior del string.

Console.WriteLine($"Tu numero de Documento es : {numeroDocumento}");

Función con cuerpo de tipo expresión

Esta funcionalidad nos permitiría reducir muchas líneas de código. Esta nueva característica se usaría solo cuando el método tiene una simple sentencia. 


public override string ToString() => $"{Capital}, {Pais}";


Usando static

Esto sí lo tenemos por suerte en Java. 

using static System.Math;

Y usarlo, de esta forma, por ejemplo:

return 5 *  PI * radius;

Operador condicional para Null

Cuando usamos objetos, hay mucha probabilidad de una excepción NullPointerException. En C# 6 se introdujo este operador de condicional para Null, de manera, que nos sirve para salvarnos de una referencia null, colocando el operador Elvis (?) después del elemento que podría darnos null. Ejemplo:

var  nombreCompleto = person?.ApellidoPaterno ?? "no disponible" ;

En este caso, sin el ?? "no disponible", un null sería asignado a la variable. 


Nameof

Si deseamos obtener el valor de la variable, como por ejemplo, cuando estamos haciendo logging, podríamos usar:

Console.Writeline($"La variable usada es {nameof(NumeroDocumento)}");


C# 7

Parámetro out

Antes de C# 7 se tenía que declarar esta variable antes de usarla. 


// antes de C# 7
string outMensaje;
OldOutParameter(out outMensaje);
Console.WriteLine(outMessage);
//C# 7: nuevo parámetro out
NewOutParameter(out string nuevoMensaje);
Console.WriteLine(nuevoMensaje);


Tuplas

Las tuplas te dan la posibilidad de retornar dos valores al mismo tiempo. Estos dos valores pueden ser de diferentes tipos de datos. Ejemplo:

var empleado = ObtenerTuplaDeRetorno();
Console.WriteLine($"Hola {empleado.nombres} tu temperatura es {empleado.temperatura}");
private static (string nombres, double temperatura) ObtenerTuplaDeRetorno()
{
    return ("Amadeo", 37.0 );
}
Para obtener acceso a los valores retornados, usa la variable.NombreDeRetorno. Ese NombreDeRetorno es el que nombre que se uso en la firma del método.

Tuplas esta disponible para .NET Framework 4.7 y superior. En caso uses una versión menor agrega esta dependencia System.ValueTuple como paquete NuGet.


Pattern Matching

Con esta característica es posible tener switch cases con tipos de datos. Por ejemplo si tenemos una lista como esta:

var listaDeObjetos = new List<object> { 25, "JoeDayz", true, 500.35};

Podemos aplicar esta característica en el switch como veremos a continuación:

double sum = 0;
foreach (var item in listaDeObjetos)
{
    switch (item)
    {
        case int val:
               sum += val;
               break;
        case double val:
               sum += val;
               break;
       case bool val:
               Console.WriteLine($"Esta activo: {val}");  
               break;
        case string val:
               Console.WriteLine($"Esta lista contiene el siguiente string: {val}");
               break;
    }
}
Console.WriteLine($"La suma es {sum}");

Mejoras en Literales

Esto sí ya lo tenemos en Java. Es poder separar números con "_" para mejorar la lectura:

const int billon = 1_000_000_000;
const int hexadecimal = 0x500_123;
Console.WriteLine(billon);
Console.WriteLine(hexadecimal);

Esos números son tratados de forma normal. Si tu imprimes esos números, se imprimirán sin el underscore ("_").


C# 8

Tipos de Referencia Nullable

En C# 8.0 se agrego tipos de referencia nullable que nos permite evitar referencias null en tiempo de compilación. Con esta nueva característica podemos marcar una propiedad como nullable o no nullable. 
Por ejemplo:

public class Person 
{
     public string Nombres { get; set; }
     public string Documento { get; set;} 
}

Esta nueva característica es opcional. Así que tenemos que habilitarla para usarla. 

<PropertyGroup>
   <OutputType>Exe</OutputType>
   <TargetFramework>netcoreapp3.0</TargetFramework>
   <Nullable>enable</Nullable>
</PropertyGroup>

Si de agrega este tag, el compilador ya nos advierte con efectos visuales sobre estas propiedades. Y si deseas que sean null puedes usar el operador Elvis para marcarlas como posibles null, lo que eliminará los warnings.

public class Person 
{
     public string? Nombres { get; set; }
     public string? Documento { get; set;} 
}

En caso lo quieras usar en un método, el compilador también te advertirá de ello:

private static void NullableReferencyTypes()
{
        var person = new Person();
        
        Console.WriteLine($"La cantidad de Dígitos del número de Documento es {person.Documento.Length}");
}


Pattern Matching

Si bien se introdujo en C#7, en esta nueva versión se agregaron más tipos de patrones.

Patrones para Propiedades

Te permite verificar una o mas propiedades de un objeto y retornar un valor dependiendo de estas propiedades. 

public class Carro
{
      public string Nombre { get; set; }
      public int Asientos { get; set; }
}

public static class PropertyPatternDemo
{
      public static bool IsSieteAsientosAndNombreGeely(Carro carro)
      {
              return carro is { Asientos: 7, Nombre: "Geely"};
      }
}

Expresiones Switch

Se puede hacer switch sobre objetos y retornar diferentes valores, dependiendo del tipo de objeto y en las propiedades del objeto. Ejemplo:

public static class SwitchExpressionDemo
{
        public static string MostrarInformacionFigura(object objetoGeometrico)
        {
                return objetoGeometrico switch
                {
                           Triangulo triangulo => $"",
                           Rectangulo rectangulo =>  rectangulo switch 
                           {
                               when rectangulo.Length == rectangulo.Width => "Este es un cuadrado",
                               => $"Rectangulo con una longitud de {rectangulo.Length} y ancho de {rectangulo.Width}",
                            },
                           Circulo circulo => $"Circulo con un radio de {circulo.Radio}",
                           _ => "Desconocido objeto geometrico"
                 };
        }
}

Patrones Tupla

Los patrones de tupla son similares al de expresiones switch. Aquí podemos pasar dos valores (una tupla) los cuales son evaluados en el case. En el siguiente ejemplo se pasan dos colores y se retorna el resultado
de un mix de esos dos colores. 

public static class TuplePatternDemo
{
        public static Color MixColor(Color color1, Color color2)
        {
             return (color1, color2) switch
            {
                    (Color.Rojo, Color.Azul) => Color.Morado ,
                    (Color.Azul, Color.Rojo) => Color.Morado,
                    (Color.Amarillo, Color.Rojo) => Color.Naranja,
                    (Color.Rojo, Color.Amarillo) => Color.Naranja,
                    (Color.Amarillo, Color.Azul) => Color.Celeste,
( _ , _ ) when color1 == color2 => color1,
_ => Color.Desconocido
            };
        }
}

Indices y Rangos

Podemos tener un array de números de 1 al 10. Con el operador rango podemos seleccionar todos los items desde el indice 2 a 8. Es clave entender que el comienzo de un rango es inclusivo y el final es exclusivo. Es decir, un rango de [1..8] retorna el número 2 a 8. Esto porque un indice 1 nos da el número 2.  
Aunque también se puede obtener el último numero del array con [^1], esto es, usando el ^ operador.


private static void IndicesAndRange()
{
        var numbers = Enumerable.Range(1, 10).ToArray();
        var copy = numbers[..];
        var allButFirst = numbers[1..];
        var lastItemRange = numbers[^1..];
        var lastItem = numbers[^1];
        var lastThree = numbers[^3..];
        Console.WriteLine($"numbers: {string.Join(", ", numbers)}");
        Console.WriteLine($"copy: {string.Join(", ", copy)}");
        Console.WriteLine($"allButFirst: {string.Join(", ", allButFirst)}");
        Console.WriteLine($"lastItemRange: {string.Join(", ", lastItemRange)}");
        Console.WriteLine($"lastItem: {lastItem}");
        Console.WriteLine($"lastThree: {string.Join(", ", lastThree)}");
}
 
C# 9

En el Microsoft build 2020, Mads Torgesen, Program Manager para el lenguaje C# y Dustin Campbel, Principal Ingeniero de Software del equipo .NET Developer Experience mostraron las nuevas características de C# 9.0 que serán entregadas con .NET 5 en Noviembre 2020.

A la fecha Java 14 ya introdujo Records, así que la cosa se pone buena. Pero, veamos que tanto se parecen. 

Pattern matching 

No hay necesidad de declarar un identificador por defecto en un switch

// Antes
public static decimal CalculateTotal(object product) =>
product switch
{
...
LaptopProduct l when t.ScreenSize > 13 => 500 + 50,
LaptopProduct l when t.ScreenSize > 15 => 500 + 75,
LaptopProduct _ => 500,
_ => throw new ArgumentException("Not a known Product type", nameof(product))
}

En C# 9.0:

LaptopProduct => 500

No hay necesidad de un '_'.


Patrones Relacionales


Se podrá usar operadores (<, >, etc) cómo indicadores de patrones:
LaptopProduct t when t.ScreenSize switch
{
    > 13 => 500 + 50,
    > 15 => 500 + 75,
   _  => 500,
},
Patrones Lógicos

Se puede combinar operadores relacionales con operadores lógicos and, or, y not:
LaptopProduct t when t.ScreenSize switch
{
    > 13 and <=15 => 500 + 50,
    > 15 => 500 + 75,
    _  => 500,
},

También podemos usar el patrón not con el patrón constante null:

    not null => throw new ArgumentException($"No es un tipo de Laptop conocido: {product}", nameof(product)),
    null => throw new ArgumentNullException(nameof(product))

Y también en lugar de usar:

    if(!(product is LaptopProduct))

podemos escribir:

    if(product is not LaptopProduct)

Sentencias de Alto Nivel

Para hacer un simple Hola Mundo, antes teníamos que hacer esto:

using System;
class Program
{
        static void Main()
        {
                Console.WriteLine("Hola Mundo");
        }
}

C# 9.0:

using System;
Console.WriteLine("Hola Mundo");

En general podemos tener cualquier sentencia , pero, sólo en un archivo, porque solo podemos tener un método main.

Omitir tipo "new"

Se puede omitir el tipo después del new porque se infiere del contexto.

//antes
Person person = new Person("Juan", "Perez");
//C# 9.0
Person person = new ("Juan", "Perez");

Validación de parámetro null

Con 9.0, el "!" al final del parámetro nos dirá que este no acepta null. 

//antes
public Person(string firstName!, string lastName!);
//C# 9.0
Person person = new ("Juan", "Perez");

Establecer valores solo en Constructor o Object Initializer: Init Accessor

En C#, para que los inicializadores de objetos trabajen, las propiedades deben ser mutables, con esa limitación, esta puede ser cambiada después con intención o sin intención.

Para solucionar esto, C# 9.0 introduce el new accessor 'init' el cual hace esto inmutable y puede ser usado cómo:


//antes
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
new Person
{
    FirstName = "Juan",
    LastName = "Perez"
}  
//cambiando valor
person.LastName = "Diaz"

En C# 9.0

//antes
public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}
new Person
{
    FirstName = "Juan",
    LastName = "Perez"
}  
//ESTO NO SE PERMITIRA CON EL INIT ACCESSOR 
//person.LastName = "Diaz"
Con 'init' esto daría error si trataramos de cambiar el valor de la propiedad. 


Records

Son una nueva y simplificada forma para una clase C# y tipos de struct que combina los beneficios de un número de simples características. Esto puede representar datos como un registro de base de datos o alguna entidad del modelo:

Propiedades read-only => Tipos inmutables
Implementaciones de Igualdad => Igualdad de estructura
Soporte a Pattern-matching = es un pattern, switch pattern, etc.

public data class Person
{
    public string FirstName { get; init;}
     public string LastName { get; init;}
}
El keyword Data marca a una clase como un record.  Ahora veremos que beneficios nos trae ser una clase Record.

Expresión With: mutación no disruptiva

Cuando se trabaja con datos inmutables, un patrón común es crear nuevos valores a partir de valor existente que representa un nuevo estado, por ejemplo, en un objeto Persona, si deseamos un objeto con diferente apellido, solo copiamos las propiedades del objeto existente con el apellido distinto.

var personaDiferente = person with {LastName = "Ramirez"}

Igualdad basada en los valores

El método Object.Equals se llamará para cada campo en el Record de forma recursiva.
Esto significa que dos instancias de la clase Record pueden ser iguales sin ser el mismo objeto. Por ejemplo:  si comparamos dos records con Equals(person, originalPerson) el resultado será true, y si usamos ReferenceEquals(person, originalPerson) el resultado será false.

Miembros de Data

La intención de una clase Record es ser inmutable, con sólo propiedades publicas que pueden ser modificadas sin usar la expresión with. Para eso, el caso común  será tener miembros Default Public, en lugar de Private members. 

C# 9.0 nos trae una forma corta para inicializar propiedades:

public data class Person { string FirstName, string LastName };

que es lo mismo que:

public data class Person
{
    public string FirstName { get; init;}
    public string LastName { get; init;}
}

Y

public data class Person
{
    public string FirstName;
    public string LastName;
    public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
    public void Deconstruct(out string firstName, out string lastName) => (firstName, lastName) = (FirstName, LastName);
    public string LastName { get; init; }    
}
puede ser escrito en una forma mas corta:

public data class Person { string FirstName, string LastName };   (Kotlin?)

Este declarará public init auto properties, así como su constructor y deconstructor. Luego podemos crear objetos de esta manera:

var person = new Person("Juan", "Perez");
var (f, l) = person;

Expresiones With y Herencia

Usar expresiones With con clases Record y aplicando Herencia es un gran reto. 

Si clase Person hereda de la clase Employee:

public data class Person { string FirstName; string LastName; }
public data class Employe : Person {  int Salary; }

Si usamos la expresion with para crear otro record:

Person person = new Employee(FirstName="Juan", LastName="Perez", Salary=2000};
var personaDiferente = person with {LastName = "Ramirez"}

Aquí el compilador C# no tiene idea si la actual persona contiene a un Employee, y por lo tanto no copiará el salario.
Los records tienen un método virtual oculto que se crea con la "clonación" de todo el objeto. Cada tipo de record derivado sobreescribe este método para llamar al constructor de la copia de ese tipo, y el constructor copia de un record derivado llama al constructor copia del record base. Una expresión con With simplemente llama a ese método y aplica el object initializer al resultado.


Enjoy!

Joe



Share: