Translate

Mostrando las entradas con la etiqueta C#. Mostrar todas las entradas
Mostrando las entradas con la etiqueta C#. Mostrar todas las entradas

martes, 14 de enero de 2025

El Operador ?? y ??= en C#


En C#, el manejo de valores nulos es crucial para escribir código robusto y limpio. Dos herramientas poderosas que el lenguaje nos ofrece son los operadores ?? y ??=. Estos operadores no solo mejoran la legibilidad, sino que también reducen el código repetitivo.

El operador de coalescencia nula ?? se utiliza para proporcionar un valor alternativo en caso de que una expresión sea null.


var result = value ?? defaultValue;


  • Si value no es null, result será igual a value.
  • Si value es null, result tomará el valor de defaultValue.


Veamos un ejemplo: 


string? name = null;

string displayName = name ?? "Usuario por defecto";

Console.WriteLine(displayName); // Salida: Usuario por defecto


El operador ??= fue introducido en C# 8.0, el operador de asignación de coalescencia nula simplifica el proceso de asignar un valor a una variable solo si esta es null.


variable ??= value;

Seria como : 


variable = (variable == null) ? value : variable


  • Si variable es null, se le asignará value.
  • Si variable ya tiene un valor, no se realiza ninguna acción.


Veamos un ejemplo: 


List<int>? numbers = null;

numbers ??= new List<int>();

numbers.Add(42);


Console.WriteLine(string.Join(", ", numbers)); // Salida: 42


Los operadores `??` y `??=` son herramientas esenciales para trabajar con valores nulos de manera elegante y eficiente en C#. Su uso adecuado no solo mejora la legibilidad del código, sino que también ayuda a prevenir errores comunes relacionados con referencias nulas.


martes, 7 de enero de 2025

Implementación Explícita de Interfaces en C#


La implementación explícita de interfaces en C# es una característica poderosa que permite definir miembros de una interfaz de manera que solo sean accesibles a través de la referencia de la interfaz, no directamente desde una instancia de la clase que la implementa. Esto resulta útil para resolver conflictos de nombres y mejorar la encapsulación. 

Cuando una clase implementa una interfaz, normalmente los miembros de esta interfaz se definen como públicos en la clase. Sin embargo, en algunos casos puede ser deseable que los miembros de la interfaz solo estén disponibles cuando se utiliza una referencia a la interfaz, y no desde la clase directamente. Esto es lo que permite la implementación explícita.

La sintaxis consiste en especificar el nombre de la interfaz antes del nombre del método o propiedad que se está implementando:


void NombreInterfaz.Metodo()


Veamos un ejemplo simple:


interface IA

{

    void B();

}


class MiClase : IA

{

    // Implementación explícita

    void IA.B()

    {

        Console.WriteLine("Método IA.B implementado explícitamente.");

    }

}


class Program

{

    static void Main()

    {

        MiClase obj = new MiClase();


        // No puedes llamar directamente a obj.B()

        // obj.B(); // Esto generaría un error de compilación


        // Debes convertir a la interfaz

        IA interfaz = obj;

        interfaz.B(); // Ahora funciona

    }

}


Un uso común de la implementación explícita es cuando una clase implementa varias interfaces que contienen miembros con el mismo nombre.


interface IA { void B(); }

interface IB { void B(); }


class MiClase : IA, IB

{

    void IA.B()

    {

        Console.WriteLine("IA.B llamado");

    }


    void IB.B()

    {

        Console.WriteLine("IB.B llamado");

    }

}


class Program

{

    static void Main()

    {

        MiClase obj = new MiClase();


        // Acceso explícito a través de cada interfaz

        ((IA)obj).B(); // IA.B llamado

        ((IB)obj).B(); // IB.B llamado

    }

}


Las ventajas de usar esto tenemos:

  1. Evitar conflictos de nombres: Permite implementar métodos con el mismo nombre en diferentes interfaces sin ambigüedades.
  2. Encapsulación: Los miembros de la interfaz solo son accesibles cuando la instancia de la clase se trata como una referencia a la interfaz.
  3. Separación de responsabilidades: Permite mantener lógicas separadas y claras cuando se implementan varias interfaces.

La implementación explícita de interfaces es una herramienta valiosa en C#, especialmente en escenarios donde se requiere mayor encapsulación o resolución de conflictos de nombres. Si bien puede parecer menos intuitiva al principio, ofrece flexibilidad y control sobre cómo se exponen los miembros de una interfaz.

viernes, 29 de noviembre de 2024

ca1860: Evite utilizar el método de extensión 'Enumerable.Any()'


Me llamo la atención un warnning en mi código C# que decia: "CA1860: Avoid using 'Enumerable.Any()' extension method" y yo dije why? y así nacio este post. 

Para determinar si un tipo de colección tiene algún elemento, es más eficiente y claro usar las propiedades Length, Count o IsEmpty (si es posible) que llamar al método Enumerable.Any.

Any(), que es un método de extensión, usa consultas integradas en lenguaje (LINQ). Es más eficiente confiar en las propiedades propias de la colección y también aclara la intención.

Otro tema es que esta regla es similar a CA1827: No use Count()/LongCount() cuando se pueda usar Any(). Sin embargo, esa regla se aplica al método Count() de Linq, mientras que esta regla sugiere usar la propiedad Count.


Veamos un poco de código: 

bool HasElements(string[] strings)

{

    return strings.Any(); // esto esta mal. 

}


bool HasElements(string[] strings)

{

    return strings.Length > 0; // esto esta bien. 

}


Lo que me dejo pensando es: entiendo que sea más performante no utilizar Any() siempre si tenemos propiedades como Count, Length o IsEmpty. Pero esto no es una desventaja a la hora de cambiar el tipo de colección? 

Podemos concluir que no esta bueno que un lenguaje nos ofrezca 2 o 3 formas de hacer lo mismo y luego nos este retando porque elegimos una. 

Dejo link:

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1860

lunes, 7 de octubre de 2024

Evolución de Pattern Matching en C#


El Pattern Matching es una poderosa característica de C# que permite realizar operaciones basadas en coincidencias de patrones. A medida que han pasado las versiones, esta funcionalidad se ha enriquecido significativamente. A continuación, se explica cómo ha evolucionado:

Type Pattern (C# 7.0)

Permite verificar y hacer un cast de un tipo directamente.


if (obj is int i)

{

    Console.WriteLine(i); // Desde C# 7.0

}


Switch con Pattern Matching (C# 7.0)

El switch puede realizar comparaciones de tipos.


switch (obj)

{

    case int i:

        Console.WriteLine(i);  // Desde C# 7.0

        break;

}


Property Pattern (C# 8.0)

Verifica propiedades de un objeto en un switch.


var person = new Person("John", 30);

var result = person switch

{

    { Name: "John", Age: 30 } => "Matched!", // Desde C# 8.0

    _ => "No Match"

};


Positional Pattern (C# 8.0)

Permite trabajar con tipos que tienen métodos `Deconstruct`.


public record Point(int X, int Y);


var point = new Point(1, 2);

string description = point switch

{

    (1, 2) => "Point at (1,2)",  // Desde C# 8.0

    _ => "Other point"

};


Logical Patterns (and, or, not) (C# 9.0)

Se pueden combinar patrones usando operadores lógicos.


int x = 5;

string result = x switch

{

    > 0 and < 10 => "Between 1 and 9",   // Desde C# 9.0

    _ => "Other"

};


Relational Pattern (C# 9.0)

Se usa para comparar valores numéricos directamente en los patrones.


int x = 20;

string result = x switch

{

    > 10 => "Greater than 10",  // Desde C# 9.0

    _ => "10 or less"

};



List Patterns (C# 11.0)

Trabaja con colecciones de una manera más directa.


int[] numbers = { 1, 2, 3 };

var result = numbers switch

{

    [1, 2, ..] => "Starts with 1, 2",  // Desde C# 11.0

    _ => "Other"

};


 Slice Patterns (C# 11.0)

Captura sublistas utilizando `..`.


int[] numbers = { 1, 2, 3, 4 };

var result = numbers switch

{

    [_, _, .. int[] rest] => $"Rest: {string.Join(", ", rest)}",  // Desde C# 11.0

    _ => "No Match"

};


Y falto hablar de las Guards. Un guard es una condición adicional que puedes usar dentro de un patrón de coincidencia para hacer una verificación más específica. Se utiliza con la palabra clave when. Veamos un ejemplo:


int number = 5;


string result = number switch

{

    int n when n > 0 => "Positive number",

    int n when n < 0 => "Negative number",

    _ => "Zero"

};


Console.WriteLine(result);  // Output: Positive number


El Pattern Matching ha evolucionado desde su introducción en C# 7.0, permitiendo escribir código más expresivo, con patrones que facilitan el trabajo con tipos, propiedades, listas y operadores lógicos. Las versiones más recientes, como C# 11.0, añaden aún más flexibilidad, como los List y Slice Patterns. ¡Con cada nueva versión, el Pattern Matching sigue siendo una de las características más poderosas del lenguaje!

lunes, 9 de septiembre de 2024

Comprendiendo `ref`, `out` e `in` en C#


ref, out, e in permiten controlar cómo se pasan los parámetros a los métodos, pero no es fácil recordar las diferencias de cada uno. Bueno, vamos a ver si este post puede dar luz a este asunto. 

Cuando llamamos a un método en C#, los parámetros se pueden pasar de diferentes maneras: por valor o por referencia. Entender la diferencia es crucial para controlar cómo los métodos interactúan con las variables que reciben.

  • Por Valor: El método recibe una copia del valor, por lo que los cambios dentro del método no afectan la variable original.
  • Por Referencia: El método recibe una referencia a la variable original, permitiendo modificar su valor.


ref: Pasar Parámetros por Referencia para Modificar


La palabra clave `ref` se utiliza cuando se quiere que un método tenga la capacidad de modificar la variable que se le pasa como parámetro. El parámetro debe estar inicializado antes de pasar al método.


void Incrementar(ref int numero) {

    numero++;

}


int valor = 5;

Incrementar(ref valor);

// valor ahora es 6


`ref` en C# es similar a pasar un parámetro por referencia utilizando `&` en C++, lo que permite la modificación del valor original dentro del método.

`out` se usa cuando un método necesita devolver un valor a través de un parámetro. A diferencia de `ref`, el parámetro no necesita estar inicializado antes de pasarse al método.

Por ejemplo: 

void AsignarValor(out int resultado) {

    resultado = 10;

}


int valor;

AsignarValor(out valor);

// valor ahora es 10


in: Pasar Parámetros por Referencia para Solo Lectura

La palabra clave `in` se usa para pasar un parámetro por referencia de manera que el método pueda leer el valor, pero no modificarlo. Esto es útil para evitar copias innecesarias de grandes estructuras de datos, mientras se asegura que no se alterarán.


void MostrarValor(in int valor) {

    Console.WriteLine(valor);

}


int numero = 5;

MostrarValor(in numero);

// Imprime 5


`in` se puede comparar con el uso de referencias constantes en C++ (`const int&`), donde la función puede leer el valor, pero no modificarlo.


ref` y out permiten que un método modifique el valor de un parámetro, pero `ref` requiere que la variable esté inicializada antes de ser pasada, mientras que `out` no.

in permite la pasada por referencia para mejorar la eficiencia (especialmente con grandes estructuras), pero no permite la modificación del valor, mientras que `ref` sí.

Debemos usar ref` cuando necesites que el método pueda leer y modificar el valor original.  Y out cuando quieras devolver un valor a través de un parámetro que no necesita estar inicializado.

Y  in, cuando se quiere pasar grandes estructuras de datos por referencia para evitar copias, pero asegurándote de que el método no pueda modificar el valor.

Este conocimiento permite a los desarrolladores de C# escribir métodos más flexibles y eficientes, optimizando el rendimiento y controlando cómo se manipulan los datos.

viernes, 30 de agosto de 2024

Primary constructors in C# 12 very similar to Scala constructors


With the arrival of C# 12, the language has introduced several new features that make programming more concise and expressive. Among them, Primary Constructors stand out for simplifying the way classes initialize their members. This feature is similar to what Scala has long offered with its primary constructors.

Primary Constructors allow a constructor to be defined directly in the class declaration, which reduces the need for repetitive code and simplifies the definition of immutable classes.


public class Person(string name, int age)

{

    public string Name { get; } = name;

    public int Age { get; } = age;

}


In this example, the `Person` class has a primary constructor that takes two parameters: name and age. And the Name and Age properties are initialized directly from the constructor parameters, making it cleaner and more concise.

Scala has offered a similar concept since its earliest versions. In Scala, the main constructor parameters are defined along with the class and can be used to initialize the class members directly.


class Person(val name: String, val age: Int)


Both C# 12 and Scala eliminate the need to define a separate constructor and assign the parameters to the class properties manually.

But in C#, properties are assigned inside the class body using an explicit assignment (= name;), while in Scala, this assignment is implicit. And in Scala, you can control the visibility of constructor parameters (val or var) more directly. In C#, the default pattern is to create immutable properties with get; only.

Scala, being a more functional programming oriented language, offers features such as eliminating the need for {} braces for simple class bodies, while C# remains more verbose in its syntax.

In conclusion, the addition of Primary Constructors in C# 12 is a step in the right direction, making the language more expressive and less verbose, approaching the simplicity that Scala has offered for years. This parallel not only demonstrates the influence of functional languages on more traditional languages like C#, but also highlights the trend toward more concise, declarative programming.

And with each passing day I see C# becoming more like Scala ...

viernes, 23 de agosto de 2024

viernes, 16 de agosto de 2024

Constructores primarios en C# 12 muy parecidos a los constructores de Scala


Con la llegada de C# 12, el lenguaje ha introducido varias características nuevas que hacen que la programación sea más concisa y expresiva. Entre ellas, los Primary Constructors destacan por simplificar la forma en que las clases inicializan sus miembros. Esta característica es similar a lo que Scala ha ofrecido desde hace tiempo con sus constructores primarios.

Los constructores primarios permiten definir un constructor directamente en la declaración de la clase, lo que reduce la necesidad de código repetitivo y simplifica la definición de clases inmutables.


public class Person(string name, int age)

{

    public string Name { get; } = name;

    public int Age { get; } = age;

}


En este ejemplo, la clase `Person` tiene un constructor primario que toma dos parámetros: `name` y `age`. Y las propiedades `Name` y `Age` se inicializan directamente desde los parámetros del constructor, haciéndolo más limpio y conciso.

Scala ha ofrecido un concepto similar desde sus primeras versiones. En Scala, los parámetros del constructor principal se definen junto con la clase y se pueden utilizar para inicializar los miembros de la clase de forma directa.


class Person(val name: String, val age: Int)


Tanto C# 12 como Scala eliminan la necesidad de definir un constructor separado y asignar los parámetros a las propiedades de la clase manualmente.

Pero en C#, las propiedades se asignan dentro del cuerpo de la clase usando una asignación explícita (`= name;`), mientras que en Scala, esta asignación es implícita. Y en Scala, se puede controlar la visibilidad de los parámetros del constructor (`val` o `var`) más directamente. En C#, el patrón predeterminado es crear propiedades inmutables con `get;` solamente.

Scala, siendo un lenguaje más orientado a la programación funcional, ofrece características como la eliminación de la necesidad de llaves `{}` para cuerpos de clase simples, mientras que C# sigue siendo más detallado en su sintaxis.

En conclusión, la incorporación de Primary Constructors en C# 12 es un paso en la dirección correcta, haciendo que el lenguaje sea más expresivo y menos verboso, acercándose a la simplicidad que Scala ha ofrecido durante años. Este paralelismo no solo demuestra la influencia de los lenguajes funcionales en lenguajes más tradicionales como C#, sino que también resalta la tendencia hacia una programación más concisa y declarativa.

Y cada día que pasa veo a C# más parecido a Scala ... 

jueves, 11 de julio de 2024

Como podemos manejar las referencias nulas?


El error más frecuente en Java es NullPointerException y me imagino que en otros lenguajes alguno similar...  Para abordar esto, se han introducido estructuras y operadores que ayudan a manejar la ausencia de valores de manera más segura y explícita. 

Por ejemplo en Java se introdujo la clase `Optional` en la versión 8 para manejar valores potencialmente nulos de una manera más segura. `Optional` es un contenedor que puede o no contener un valor no nulo.

import java.util.Optional;


public class OptionalExample {

    public static void main(String[] args) {

        Optional<String> optional = Optional.of("Hello, World!");

        

        // Verificar si hay un valor presente

        if (optional.isPresent()) {

            System.out.println(optional.get());

        }

        

        // Uso del método ifPresent

        optional.ifPresent(System.out::println);

        

        // Proveer un valor predeterminado

        String value = optional.orElse("Default Value");

        System.out.println(value);

        

        // Proveer un valor predeterminado usando un Supplier

        value = optional.orElseGet(() -> "Default Value from Supplier");

        System.out.println(value);

    }

}


Scala utiliza la clase `Option` para representar un valor opcional. `Option` tiene dos subclases: `Some` y `None`, lo que proporciona una forma elegante y funcional de manejar valores que pueden estar ausentes. Esta idea es similar a la monada `Maybe` en Haskell.


object OptionExample extends App {

  val someValue: Option[String] = Some("Hello, World!")

  val noneValue: Option[String] = None


  // Uso de getOrElse

  println(someValue.getOrElse("Default Value"))

  println(noneValue.getOrElse("Default Value"))


  // Uso del patrón de coincidencia (Pattern Matching)

  someValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }


  noneValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }

}


Scala "copio" esta forma de Haskell. Haskell utiliza el tipo de datos `Maybe` para manejar valores opcionales `Maybe` puede ser `Just` un valor o `Nothing`.


main :: IO ()

main = do

    let someValue = Just "Hello, World!"

    let noneValue = Nothing


    -- Uso de fromMaybe

    putStrLn (fromMaybe "Default Value" someValue)

    putStrLn (fromMaybe "Default Value" noneValue)


    -- Uso del patrón de coincidencia (Pattern Matching)

    case someValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


    case noneValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


Kotlin es similar a Scala en muchos aspectos pero no en este. Kotlin introduce el operador `?` para facilitar la gestión de valores nulos. Este operador se utiliza para declarar tipos de datos que pueden ser nulos y para realizar operaciones seguras contra nulos.


fun main() {

    var nullableString: String? = "Hello, World!"


    // Uso del operador ?. para llamadas seguras

    println(nullableString?.length)


    // Uso del operador ?: para proporcionar un valor predeterminado

    val length = nullableString?.length ?: 0

    println(length)


    nullableString = null


    // Uso de let para ejecutar código solo si el valor no es nulo

    nullableString?.let {

        println(it)

    }

}


C# ha incluido varias características para manejar valores nulos, como el operador `?`, que facilita el manejo seguro de tipos que pueden ser nulos.


using System;


class Program

{

    static void Main()

    {

        string? nullableString = "Hello, World!";

        

        // Uso del operador ?. para llamadas seguras

        Console.WriteLine(nullableString?.Length);


        // Uso del operador ?? para proporcionar un valor predeterminado

        int length = nullableString?.Length ?? 0;

        Console.WriteLine(length);


        nullableString = null;


        // Uso de pattern matching para verificar nulos

        if (nullableString is string nonNullString)

        {

            Console.WriteLine(nonNullString);

        }

    }

}


Rust maneja la ausencia de valores y los errores de una manera robusta utilizando los tipos `Option` y `Result`. `Option` puede ser `Some` o `None`, mientras que `Result` puede ser `Ok` o `Err`.


fn main() {

    let some_value: Option<String> = Some("Hello, World!".to_string());

    let none_value: Option<String> = None;


    // Uso de unwrap_or

    println!("{}", some_value.unwrap_or("Default Value".to_string()));

    println!("{}", none_value.unwrap_or("Default Value".to_string()));


    // Uso del patrón de coincidencia (Pattern Matching)

    match some_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }


    match none_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }

}


Go no tiene un tipo de datos específico para manejar valores opcionales, pero utiliza la convención de retornar múltiples valores, incluyendo un valor y un `error`. Que la verdad no me gusta, te pasas preguntando todo el tiempo si hay error o si los valores son nulos. 


package main


import (

    "errors"

    "fmt"

)


func getValue() (string, error) {

    return "Hello, World!", nil

}


func getNullableValue() (string, error) {

    return "", errors.New("no value")

}


func main() {

    value, err := getValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", value)

    }


    nullableValue, err := getNullableValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", nullableValue)

    }

}


Python utiliza la palabra clave `None` para representar la ausencia de valor. Aunque no tiene una estructura específica como `Optional`, los desarrolladores pueden utilizar condicionales y manejo de excepciones.


def get_value():

    return "Hello, World!"


def get_nullable_value():

    return None


value = get_value()

nullable_value = get_nullable_value()


if value is not None:

    print(value)

else:

    print("Default Value")


if nullable_value is not None:

    print(nullable_value)

else:

    print("Default Value")


Ruby utiliza `nil` para representar la ausencia de valor. Al igual que en Python, no tiene una estructura específica para valores opcionales, pero proporciona métodos para manejar `nil`.


value = "Hello, World!"

nullable_value = nil


# Uso del operador ||

puts value || "Default Value"

puts nullable_value || "Default Value"


# Uso de condicionales

puts value.nil? ? "Default Value" : value

puts nullable_value.nil? ? "Default Value" : nullable_value


C++ utiliza punteros inteligentes (`smart pointers`) para gestionar la memoria y prevenir errores relacionados con punteros nulos. Los punteros inteligentes, como `std::unique_ptr` y `std::shared_ptr`, se encargan de la gestión automática de la memoria.


#include <iostream>

#include <memory>


int main() {

    std::unique_ptr<int> uniquePtr(new int(42));

    if (uniquePtr) {

        std::cout << *uniquePtr << std::endl;

    }


    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);

    if (sharedPtr) {

        std::cout << *sharedPtr << std::endl;

    }


    // Uso de weak_ptr para evitar ciclos de referencia

    std::weak_ptr<int> weakPtr = sharedPtr;

    if (auto lockedPtr = weakPtr.lock()) {

        std::cout << *lockedPtr << std::endl;

    }


    return 0;

}


TypeScript, un superconjunto de JavaScript, permite tipos opcionales y tiene un soporte robusto para manejar valores `null` y `undefined`.


let nullableString: string | null = "Hello, World!";


// Uso del operador ? para llamadas seguras

console.log(nullableString?.length ?? 0);


// Uso de if para asegurar valores no nulos

if (nullableString !== null) {

    console.log(nullableString);

}


TypeScript utiliza tipos opcionales para manejar valores que pueden ser `null` o `undefined`, proporcionando un enfoque seguro para evitar errores comunes relacionados con valores nulos. El operador `?.` permite realizar llamadas seguras, y el operador `??` proporciona valores predeterminados en caso de valores `null` o `undefined`.

En fin, aunque la gestión de valores nulos varía entre lenguajes, la idea subyacente es la misma: proporcionar mecanismos más seguros y expresivos para manejar la ausencia de valores. Ya sea mediante clases contenedoras como `Optional` en Java y `Option` en Scala, tipos de datos como `Maybe` en Haskell, operadores específicos como `?` en Kotlin y C#, punteros inteligentes en C++, o enfoques específicos en Rust, Go, Python y Ruby, estos enfoques ayudan a reducir los errores y a escribir un código más robusto y mantenible.


lunes, 20 de mayo de 2024

Lenguajes utilizados en los proyectos apache

Tal vez hace mucho que Apache publico este gráfico pero yo recién lo veo : 



Como se puede ver Java es el lenguaje más utilizado por los proyectos de apache, seguido de python, c++, c, javascript, scala, C#, go, perl, etc ... 




miércoles, 8 de mayo de 2024

Que es la Covarianza, Contravarianza y Invarianza?


Ya sé que estos son temas super basicos pero siempre que hablan de eso me los confundo. Por eso voy a hacer este post para ver si porfin puedo fijar las ideas. 

La varianza y la covarianza son conceptos importantes en el contexto de los tipos genérico. Estos conceptos se refieren a cómo se relacionan los tipos genéricos cuando se consideran subtipos y super tipos. Veamos una explicación de cada uno:

Covarianza: La covarianza se refiere a la relación entre tipos genéricos donde la relación de subtipos se mantiene en la misma dirección que la relación de subtipos de los parámetros de tipo. En otras palabras, si tenemos un tipo T y otro tipo U donde U es un subtipo de T, entonces podemos decir que List<U> es un subtipo de List<T>. Esto significa que podemos asignar una lista de subtipos a una lista de supertipos sin necesidad de conversión explícita. La covarianza se utiliza típicamente en situaciones donde solo se lee de una estructura de datos, como en secuencias o enumeraciones.

Veamos un ejemplo en C#: 

using System;

using System.Collections.Generic;


class Program

{

    static void Main()

    {

        // Covarianza en IEnumerable<T>

        IEnumerable<string> strings = new List<string> { "hello", "world" };

        PrintItems(strings);

    }


    static void PrintItems(IEnumerable<object> items)

    {

        foreach (var item in items)

        {

            Console.WriteLine(item);

        }

    }

}

Veamos un ejemplo en Java: 

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Covarianza en List<? extends Number>
        List<Integer> integers = new ArrayList<>();
        integers.add(1);
        integers.add(2);
        printNumbers(integers);
    }

    static void printNumbers(List<? extends Number> numbers) {
        for (Number number : numbers) {
            System.out.println(number);
        }
    }
}

Y en scala: 

class Main {
  def main(args: Array[String]): Unit = {
    // Covarianza en List[+A]
    val strings: List[String] = List("hello", "world")
    printItems(strings)
  }

  def printItems(items: List[Any]): Unit = {
    items.foreach(println)
  }
}

Contravarianza: La contravarianza es el opuesto de la covarianza. Se refiere a la relación entre tipos genéricos donde la relación de subtipos se invierte en comparación con la relación de subtipos de los parámetros de tipo. En otras palabras, si tenemos un tipo T y otro tipo U donde U es un subtipo de T, entonces podemos decir que Action<T> es un subtipo de Action<U>. Esto significa que podemos asignar una función que acepta supertipos a una variable de función que acepta subtipos. La contravarianza se utiliza típicamente en situaciones donde solo se escribe en una estructura de datos, como en consumidores de datos o comparadores.

Veamos un ejemplo en Java: 


import java.util.function.Consumer;


public class Main {

    public static void main(String[] args) {

        // Contravarianza en Consumer<? super String>

        Consumer<Object> printString = Main::printString;

        printString.accept("hello");

    }


    static void printString(Object s) {

        System.out.println(s);

    }

}


Y en Scala: 

class Main {
  def main(args: Array[String]): Unit = {
    // Contravarianza en Function1[-T, +R]
    val printString: Any => Unit = Main.printString
    printString("hello")
  }
}

object Main {
  def printString(s: String): Unit = {
    println(s)
  }
}

Invarianza: La invarianza es el tercer escenario, donde no hay una relación de subtipos entre tipos genéricos. En otras palabras, List<T> no es ni un subtipo ni un supertipo de List<U> si T y U son tipos diferentes, incluso si U es un subtipo de T o viceversa. En este caso, necesitamos una conversión explícita para asignar entre los dos tipos.

En C#, la covarianza y la contravarianza se expresan a través de modificadores como out y in en declaraciones de tipo genérico. En Java, estos conceptos se implementan a través de la notación <? extends T> para la covarianza y <? super T> para la contravarianza en tipos genéricos. Es importante entender estos conceptos para escribir código genérico seguro y comprensible.

No recuerdo haber utilizado la contravarianza pero no se me hace algo tan común. Y ustedes, han utilizado estas técnicas? 


miércoles, 24 de abril de 2024

Correr todos los métodos main de un paquete en C#


Supongamos que tenemos un conjunto de clases que tienen un método main o se puede llamar como quiera y nosotros queremos pasar por parámetro el nombre de la clase y si no pasamos ningun nombre se ejecutan todos los métodos:


 using System;

using System.Linq;

using System.Reflection;



namespace Example

{

    class Program

    {

        static void Main(string[] args)

        {

            if (args[0] != null)

            {

                Run(args[0]);

            }

            else

            {

                var types = AppDomain.CurrentDomain.GetAssemblies()

                    .SelectMany(assembly => assembly.GetTypes())

                    .Where(type => "Example".Equals(type.Namespace)

                                   && !"Program".Equals(type.Name)).OrderBy(type => type.Name);

                foreach (var type in types)

                {

                    Run("Example."+type.Name);

                }

            }

        }


        private static void Run(string className)

        {

            var classType = Type.GetType(className);

            var declaredMethods = (classType as System.Reflection.TypeInfo)?.DeclaredMethods;

            if (declaredMethods == null) return;

            var mainMethod = declaredMethods.FirstOrDefault(method => method.Name == "Main");

            if (mainMethod == null)

            {

                return;

            }

            mainMethod.Invoke(null, null);

        }

    }

}

El método Run corre el metodo main de la clase pasada por parametros. Si no tiene este método, no hace nada. 

Listo!!  

viernes, 12 de enero de 2024

¡C# es el lenguaje de programación del año 2023!

Como cada año se publica el indice tiobe que indica el uso de los lenguajes. Y este año la novedad fue el amplio crecimiento de C#. 

"Por primera vez en la historia del índice TIOBE, C# ha ganado el premio al lenguaje de programación del año. ¡Felicidades! C# ha estado entre los 10 mejores durante más de 2 décadas y ahora que se está poniendo al día con los 4 grandes lenguajes, ganó el merecido premio al ser el lenguaje con el mayor repunte en un año (+1,43%). Los segundos puestos son Scratch (+0,83%) y Fortran (+0,64%). C# le está quitando cuota de mercado a Java y se está volviendo cada vez más popular en dominios como backends de aplicaciones web y juegos (gracias a Unity). C# se puede utilizar de forma gratuita y evoluciona a un ritmo constante, lo que hace que el lenguaje sea más expresivo con cada nueva versión. C# llegó para quedarse y pronto podría incluso superar a Java.

Aparte de C#, el año pasado hubo muchos cambios interesantes en el índice TIOBE. Fortran y Kotlin se convirtieron permanentemente en los 20 mejores jugadores reemplazando a los antiguos favoritos R y Perl. Fortran está muy preparado para hacer cálculos con buenas bibliotecas y sigue siendo uno de los favoritos de las universidades en muchos ámbitos. Kotlin es el competidor de Java fácil de aprender y escribir. Pregunta interesante: ¿qué lenguajes entrarán en el top 20 del índice TIOBE en 2024? Esto es muy difícil de predecir. Julia tocó brevemente el índice TIOBE en 2023, pero no pudo mantener esa posición. Se necesita la madurez del idioma y la comunidad de Julia para tener una segunda oportunidad. Apostaría por Dart (con Flutter) y TypeScript. Este último ya se utiliza mucho en la industria, pero por alguna razón todavía no se destaca en el índice TIOBE. Veamos qué nos depara el 2024." -- Paul Jansen, director ejecutivo de TIOBE Software (Esto nos dice la pagina oficial de TIOBE.)

Como lenguje más utilizado lo tenemos a python, seguido de c, c++ y java. 


Jan 2024Jan 2023ChangeProgramming LanguageRatingsChange
11Python pagePython13.97%-2.39%
22C pageC11.44%-4.81%
33C++ pageC++9.96%-2.95%
44Java pageJava7.87%-4.34%
55C# pageC#7.16%+1.43%
67changeJavaScript pageJavaScript2.77%-0.11%
710changePHP pagePHP1.79%+0.40%
86changeVisual Basic pageVisual Basic1.60%-3.04%
98changeSQL pageSQL1.46%-1.04%
1020changeScratch pageScratch1.44%+0.86%
1112changeGo pageGo1.38%+0.23%
1227changeFortran pageFortran1.09%+0.64%
1317changeDelphi/Object Pascal pageDelphi/Object Pascal1.09%+0.36%
1415changeMATLAB pageMATLAB0.97%+0.06%
159changeAssembly language pageAssembly language0.92%-0.68%
1611changeSwift pageSwift0.89%-0.31%
1725changeKotlin pageKotlin0.85%+0.37%
1816changeRuby pageRuby0.80%+0.01%
1918changeRust pageRust0.79%+0.18%
2031changeCOBOL pageCOBOL0.78%+0.45%



La verdad es que descreo un poco de este indice, por ejemplo me llama la atención que Rust bajo, cuando en general veo que va creciendo mucho. O por ejemplo que sea más utilizado Scratch que Go o Rust. Son cositas que no me cierran :( 


Dejo link: https://www.tiobe.com/tiobe-index/

jueves, 16 de febrero de 2023

¿Cuál es la diferencia entre "is not null" y "!= null" en C# ?



La principal diferencia entre e != null y is not null es la forma en que el compilador ejecuta la comparación.

El compilador garantiza que no se invoque ningún operador de igualdad == sobrecargado por el usuario cuando se evalúa la expresión x es nula.

Es decir si sobre escriben mal == estamos muertos, por eso esta bueno siempre usar "is not null" o "is null" 

Veamos un ejemplo: 

public class TestObject

{

  public string Test { get; set; }


  // attempt to allow TestObject to be testable against a string

  public static bool operator ==(TestObject a, object b)

  {

    if(b == null)

      return false;

    

    if(b is string)

      return a.Test == (string)b;


    if(b is TestObject)

      return a.Test == ((TestObject)b).Test;


    return false;

  }


  public static bool operator !=(TestObject a, object b)

  {

    if(b == null)

      return false;

    

    if(b is string)

      return a.Test != (string)b;


    if(b is TestObject)

      return a.Test != ((TestObject)b).Test;


    return false;

  }

}


Como vemos la sobrecarga de == y != cuando b es nulo, retorna false y eso esta mal. Por lo tanto, si ejecutamos el siguiente código:


TestObject e = null;

if(e == null)

  Console.WriteLine("e == null");

if(e is null)

  Console.WriteLine("e is null");


El Output va ser : e is null

Y si hacemos : 


TestObject e = new TestObject();

if(e != null)

  Console.WriteLine("e != null");

if(e is not null)

  Console.WriteLine("e is not null");


El Output va ser: e is not null

Ninguno de los operadores sobrecargados se implementa "correctamente", por lo que la consola nunca genera e == null o e != null.