¿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:

0 comentarios:

Publicar un comentario