Translate

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

martes, 1 de julio de 2025

¿Cuándo usar record en C#?


Desde C# 9, el lenguaje introdujo una nueva palabra clave: record. A simple vista parece una forma alternativa de declarar clases, pero su objetivo es mucho más específico: modelar datos inmutables que se comparan por valor.

Un record es un tipo de referencia que, a diferencia de las clases tradicionales, se compara por valor y está orientado a la inmutabilidad.


public record Person(string Name, int Age);


var p1 = new Person("Ada", 30);

var p2 = new Person("Ada", 30);


Console.WriteLine(p1 == p2); // True


Con clases normales (class), esta comparación daría false, porque se comparan las referencias.


Usá record cuando:

  • Te interesa la comparación por valor: Por ejemplo, para representar coordenadas, dinero, personas, productos, etc.
  • No necesitás mutabilidad. Los record son inmutables por defecto (aunque podés crear record class mutables, no es lo ideal).
  • Estás trabajando con código funcional o DDD. Es perfecto para modelar objetos de valor (value objects).
  • Querés usar with para copiar fácilmente con cambios:


var p3 = p1 with { Age = 31 };


 ¿Cuándo no usar record?

  • Cuando necesitás una identidad mutable (por ejemplo, una entidad con un Id que cambia de estado).
  • Cuando estás trabajando con frameworks como EF Core que esperan clases tradicionales para el mapeo.
  • Cuando necesitás herencia compleja (los record funcionan bien, pero pueden agregar confusión si no se usan con cuidado).


Desde C# 10, también existe record struct: un tipo por valor que se comporta como record, pero como struct.


public readonly record struct Coordinates(int X, int Y);


Ideal para representar datos livianos inmutables, como vectores o puntos.


Si estás modelando datos que no cambian, donde la igualdad semántica importa más que la identidad, y querés un código limpio, expresivo y seguro, record es la herramienta perfecta.


sábado, 28 de junio de 2025

El patrón Value Object… y cómo Ruby se lo salta cuando quiere


En el diseño orientado a objetos, un Value Object representa una entidad inmutable, comparada por valor y sin identidad propia (es decir, su identidad es el valor). Es un patrón común en lenguajes como Java, C# y Kotlin. 

Por ejemplo si necesitamos representar una fecha, dinero, fracciones, números complejos, etc. usaremos este patrón. 


Un Value Object tiene tres características esenciales:

  • Inmutabilidad: su estado no cambia después de ser creado.
  • Comparación por valor: dos objetos con los mismos atributos se consideran iguales.
  • Ausencia de identidad propia: no importa quién lo creó ni cuándo, solo importa su valor.


En C# con record, tenés inmutabilidad y comparación por valor automáticamente:


public record Money(decimal Amount, string Currency);


var a = new Money(10, "USD");

var b = new Money(10, "USD");


Console.WriteLine(a == b); // True


Y no podés modificar sus valores.


En Ruby, podés definir una clase que parezca un Value Object:


class Money

  attr_reader :amount, :currency


  def initialize(amount, currency)

    @amount = amount

    @currency = currency

  end


  def ==(other)

    amount == other.amount && currency == other.currency

  end

end


Hasta ahí todo bien, pero... todo es mutable


usd = Money.new(100, "USD")

usd.instance_variable_set(:@amount, 999) # ¡Booom!


Ruby no impide modificar los atributos internos con metaprogramación. Incluso podés cambiar el comportamiento de un único objeto:


usd.define_singleton_method(:amount) { 0 }


Este tipo de cosas rompen completamente la idea de inmutabilidad.

Entonces, ¿Cómo hacer que un VO en Ruby sea más seguro? No hay garantía total, pero hay algunas medidas:


class SafeMoney

  attr_reader :amount, :currency

  def initialize(amount, currency)

    @amount = amount.freeze

    @currency = currency.freeze

    freeze

  end


  def ==(other)

    amount == other.amount && currency == other.currency

  end

end


  • freeze impide modificaciones.
  • freeze también se aplica a los valores internos.
  • El objeto completo se congela con freeze.


Aun así... no es 100% a prueba de balas. Ruby confía en vos.

Ruby es expresivo, flexible y poderoso. Pero esa flexibilidad puede ser peligrosa cuando aplicás patrones pensados para lenguajes más rígidos.

¿Querés un Value Object en Ruby? Podés tener algo parecido, pero tené en cuenta que, la inmutabilidad en Ruby es un acto de fe... y freeze es tu mejor aliado.

Y por ultimo un recuerdo de cuando aprendi Ruby: Antes de Ruby 2.4, existían dos clases distintas para representar enteros: Fixnum (para enteros pequeños) y Bignum (para enteros grandes). Aunque eran objetos y permitían monkey patching, seguían siendo inmutables, y si se intentaba forzar una mutación real, el programa fallaba.


class Fixnum

  def mutate!

    self.replace(99)  # Esto explota

  end

end


5.mutate!

# => Error: can't modify frozen Integer (TypeError)


Incluso si se intentaba hacer algo como modificar self o reemplazar el contenido del número, Ruby lo impedía, ya que los enteros eran internamente inmutables y congelados. Esto confundía a quienes veían métodos con ! en otros objetos como String, donde sí había mutabilidad real.

Desde Ruby 2.4, Fixnum y Bignum se unificaron en Integer, y aunque el comportamiento de inmutabilidad se mantiene, ahora es más claro que los enteros no pueden ni deben ser mutados.

jueves, 26 de junio de 2025

Asignaciones a this en structs en C#


En C#, solemos pensar que this es una entidad inmutable dentro de un objeto: podés usarlo para leer, pero jamás para asignar. Eso es cierto… salvo que estés trabajando con un struct. Y ahí es donde empieza lo interesante.

En clases, intentar escribir this = otraInstancia; es un error de compilación. Pero en structs, esa línea es válida, y en algunos casos, incluso útil.

¿Por qué? Porque un struct es un tipo de valor. Cuando estás dentro de un método de instancia de un struct, this es una referencia a una copia de la instancia, y podés reasignarla.

Veamos un ejemplo básico


struct Punto

{

    public int X;

    public int Y;


    public void Reiniciar()

    {

        this = new Punto(0, 0); // reemplaza toda la instancia

    }

}


Este método Reiniciar reemplaza completamente el contenido del struct por uno nuevo.


¿Y esto para qué sirve? A veces, en vez de ir campo por campo, simplemente querés decir:

Ya fue, esta instancia no me sirve, la reescribo entera.


Por ejemplo:


struct Usuario

{

    public string Nombre;

    public string Email;


    public void Vaciar()

    {

        this = default; // equivalente a new Usuario()

    }

}


C# 9 introdujo record struct, y con eso también podemos usar el patrón with, lo que abre otras posibilidades:


public readonly record struct Coordenada(int X, int Y)

{

    public Coordenada Mover(int dx, int dy)

    {

        return this with { X = this.X + dx, Y = this.Y + dy };

    }

}


¿Y si no querés devolver una nueva instancia? Podés mutar así:


public record struct Contador(int Valor)

{

    public void Incrementar()

    {

        this = this with { Valor = Valor + 1 };

    }

}


  • Esto solo funciona en structs, no en clases.
  • Al reasignar this, perdés todo el estado anterior.
  • No es común en código idiomático de C#, así que usalo con intención y claridad.


La asignación a this en structs es uno de esos detalles de C# que sorprenden. No es algo que debas usar todo el tiempo, pero saber que existe puede ayudarte a escribir código más claro en ciertos contextos.

La próxima vez que necesites reiniciar por completo una instancia de un struct, acordate:

Sí, podés decir this = ... y nadie te lo va a impedir.

viernes, 20 de junio de 2025

this = default en structs: un truco válido (y útil) que no conocías de C#


Si venís trabajando con C# desde hace un tiempo, probablemente sepas que this en un método de instancia no se puede modificar. Es una referencia al objeto actual, y es inmutable… ¿o no?

Bueno, hay una excepción sorprendente: en los constructores de structs, this puede ser reasignado. Así es: podemos hacer this = default; y reinicializar por completo una instancia.


¿Qué significa this = default;?


Cuando hacemos esto en un struct:


this = default;


Le estamos diciendo al compilador que queremos resetear todos los campos del struct a sus valores por defecto:

  • int, float, double → 0
  • bool → false
  • objetos → null
  • otros structs → default también


¿Por qué es válido?


Porque los structs en C# son tipos de valor, no de referencia como las class. En los constructores de un struct, el compilador permite esta asignación especial a this porque se trata de una variable local implícita, y su valor es mutable dentro del constructor.


¿Y por qué no se puede hacer en class?


Porque en una clase, this es una referencia al objeto actual, y C# no te permite cambiar esa referencia. Sería como tratar de asignar una nueva dirección de memoria a una variable inmutable.


this = default; es útil cuando:

  • Querés evitar código repetido asignando valores por defecto.
  • Querés cortar un constructor temprano y dejar el objeto en estado "vacío" o neutro.
  • Trabajás con structs grandes o generics, y querés evitar errores al inicializar manualmente cada campo.


this = default puede parecer extraño al principio, pero es una herramienta válida y muy útil para inicializar structs de manera limpia y segura. No es magia, es C# aprovechando las reglas especiales de los tipos de valor.


lunes, 16 de junio de 2025

¿Qué es fixed en C#?


La palabra clave fixed en C# se usa en dos contextos, ambos relacionados con el trabajo de bajo nivel y el manejo de memoria no administrada (unsafe code).


Para obtener un puntero fijo a una variable administrada

En C#, los objetos administrados pueden moverse en memoria (por el Garbage Collector). Si queremos trabajar con punteros (como en C o C++), necesitamos asegurarnos de que el objeto no se mueva durante el uso del puntero.

Ahí entra fixed, le indica al runtime que fije el objeto en memoria.


unsafe 

{

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


    fixed (int* p = numeros)

    {

        for (int i = 0; i < 4; i++)

        {

            Console.WriteLine(*(p + i));

        }

    }

}


  • fixed bloquea el arreglo numeros en memoria.
  • int* p es un puntero al primer elemento.
  • Mientras esté dentro del bloque fixed, numeros no se moverá.


Para fijar buffers dentro de structs fixed size buffer

C# permite declarar buffers de tamaño fijo dentro de structs cuando estás trabajando en código unsafe.


unsafe struct MiBuffer

{

    public fixed byte datos[10];

}


  • Aquí datos es un array de 10 bytes directamente dentro del struct.
  • Es muy usado cuando interoperás con código nativo o estructuras de bajo nivel.


Cosas importantes:

  • Solo se puede usar fixed dentro de código marcado como unsafe.
  • Necesitás habilitar la opción /unsafe en el compilador o el flag en el proyecto.
  • Ayuda a interoperar con APIs nativas, como Win32, o trabajar con bloques de memoria directamente.
  • No es algo común en código de negocio habitual en C#, pero sí muy usado en interop, performance crítica o trabajo con hardware.



martes, 10 de junio de 2025

new vs override en C#: ¿cuál es la diferencia y cuándo usar cada uno?



Cuando trabajamos con herencia en C#, dos palabras clave aparecen para definir comportamiento en clases derivadas: new y override. A primera vista pueden parecer similares, pero representan intenciones diferentes. Entenderlas bien puede evitarte muchos errores.

En C# una clase puede heredar miembros (métodos, propiedades, etc.) de otra. ¿Pero qué pasa si queremos cambiar el comportamiento de un método heredado?

Ahí entran en juego override y new.

Cuando queremos modificar el comportamiento de un método heredado, usámos override.


class Animal

{

    public virtual void Hablar()

    {

        Console.WriteLine("Hace un sonido");

    }

}


class Perro : Animal

{

    public override void Hablar()

    {

        Console.WriteLine("Guau");

    }

}


Y ahora mirá esto:


Animal a = new Perro();

a.Hablar(); // Imprime "Guau"


Con override, el comportamiento depende del tipo real del objeto, no del tipo de la variable. Esto se llama polimorfismo.


En cambio, new oculta el método de la clase base. No lo reemplaza a nivel del sistema de tipos.


class Animal

{

    public void Hablar()

    {

        Console.WriteLine("Hace un sonido");

    }

}


class Gato : Animal

{

    public new void Hablar()

    {

        Console.WriteLine("Miau");

    }

}


Probá esto:

Animal a = new Gato();

a.Hablar(); // Imprime "Hace un sonido"


Porque a es del tipo Animal, y el método Hablar() de Gato no lo reemplazó: solo lo ocultó. Pero:


Gato g = new Gato();

g.Hablar(); // Imprime "Miau"


¿cuándo uso new?

  • Cuando querés ocultar intencionalmente un método de la clase base, pero sin reemplazarlo para el sistema de tipos.
  • Generalmente se evita, porque puede causar confusión. Se usa cuando no podés cambiar la clase base (por ejemplo, una librería externa) pero necesitás un método con el mismo nombre y diferente comportamiento.


¿Qué pasa si no usás ni new ni override? El compilador mostrará una advertencia si detecta que estás ocultando un miembro sin decirlo explícitamente.


// Esto compila, pero da warning:

public void Hablar() { ... } // Oculta un método de la base


Siempre que ocultes un miembro heredado, C# quiere que lo declares con new o override, para que tu intención sea clara.

martes, 20 de mayo de 2025

Conversiones implicit y explicit en C#


En C#, los operadores implicit y explicit nos permiten definir conversiones personalizadas entre tipos. Esto es útil cuando trabajamos con estructuras o clases que representan conceptos que pueden transformarse naturalmente en otros valores, como metros a kilómetros, grados a radianes, o monedas.

Cuando diseñamos tipos personalizados, muchas veces necesitamos que se comporten como si fueran valores comunes. Por ejemplo, si tenemos un struct Metros, queremos poder escribir:


Metros distancia = 100.0;


En lugar de tener que usar un constructor o un método auxiliar. O, si ya tenemos un objeto de tipo Metros, queremos poder convertirlo a double sin esfuerzo.

Sin conversiones definidas, tendríamos que hacer algo como:


var distancia = new Metros(100.0);

double d = distancia.Valor;


Definiendo operadores de conversión, podemos hacer que esta interacción sea más fluida.


El modificador implicit permite definir una conversión automática cuando no hay pérdida de información. El compilador permitirá esta conversión sin necesidad de cast.

Por ejemplo:


public struct Metros

{

    public double Valor { get; }


    public Metros(double valor) => Valor = valor;


    public static implicit operator Metros(double valor) => new Metros(valor);

}


Uso:

Metros distancia = 100.0; // conversión implícita desde double


Cuando una conversión puede perder información, o puede fallar, se recomienda marcarla como explicit. Esto obliga al programador a escribir el cast, dejando claro que está realizando una operación que podría tener consecuencias.

Por ejemplo:


public static explicit operator double(Metros metros) => metros.Valor;


Uso:


double d = (double)distancia; // cast explícito requerido


Las conversiones implicit y explicit en C# permiten diseñar APIs más amigables, legibles y seguras. Usá implicit cuando estés seguro de que la conversión no va a fallar ni perder datos, y explicit cuando quieras que el programador sea consciente del riesgo de la conversión.


viernes, 9 de mayo de 2025

Trabajando con partes de colecciones sin copiar: slices, spans y más


Cuando trabajamos con colecciones o buffers de datos, es muy común necesitar operar solo sobre una parte de ellos: una porción de un array, un segmento de texto, o un rango de bytes. La solución más directa suele ser copiar esa parte a una nueva estructura… pero eso introduce sobrecarga innecesaria de memoria y CPU. Si queremos trabajar con partes de una colección (por ejemplo, un array, un string o un buffer de bytes) sin crear copias, es decir, tener una vista o referencia a un fragmento de la colección original.  

Es decir, queremos qu esta solución sea:

  • Ligera en memoria (sin asignaciones adicionales)
  • Segura (sin acceder fuera de los límites)
  • Eficiente (idealmente sin costo en tiempo de ejecución)

Muchos lenguajes modernos han ido incorporando construcciones para resolver este problema. Veamos cómo lo hacen C#, Go y Rust.

A partir de C# 7.2, se introdujo Span<T>, una estructura de tipo ref struct que representa una ventana sobre memoria contigua. 


int[] datos = { 10, 20, 30, 40 };

Span<int> segmento = datos.AsSpan(1, 2); // contiene {20, 30}


Podés usar Span<T> para:

  • Evitar copiar arrays o strings.
  • Trabajar con memoria en el stack (stackalloc).
  • Reutilizar buffers en pipelines o parsers.
  • Procesar archivos grandes en trozos.


Span<byte> buffer = stackalloc byte[1024]; // sin heap


Limitaciones:

  • No puede usarse como campo de clases (sólo en structs).
  • No se puede usar con async/await ni capturar en lambdas.
  • Solo dentro del alcance del stack (por diseño).


Go tiene slices desde siempre: son una capa por encima de los arrays. Un slice guarda un puntero al array subyacente, longitud y capacidad.


arr := [5]int{1, 2, 3, 4, 5}

s := arr[1:4] // contiene {2, 3, 4}


Ventajas:

  • Livianos y eficientes.
  • Se puede modificar el contenido (afecta al array original).
  • Permiten crecer mediante append si hay capacidad.


s[0] = 99 // también cambia arr[1]


La semántica de slicing en Go es natural y permite componer operaciones sin asignar memoria.


Rust maneja este problema con referencias segmentadas: &[T] para vistas inmutables y &mut [T] para mutables.


let arr = [1, 2, 3, 4];

let slice = &arr[1..3]; // &[2, 3]


Características:

  • Completamente seguras en tiempo de compilación.
  • El borrow checker impide aliasing mutable.
  • Altamente eficientes, sin sobrecarga.
  • Son la base de muchas APIs estándar.


fn print_slice(s: &[i32]) {

    for val in s {

        println!("{}", val);

    }

}


¿Y cuál conviene?

  • Si estás en un lenguaje GC-friendly como C#, Span<T> te da poder sin pagar costo de GC.
  • Si buscás simplicidad y velocidad de desarrollo, Go es imbatible con su slicing natural.
  • Si necesitás seguridad al máximo y performance nativa, Rust con slices es lo más robusto.

Es decir, depende del lenguaje que estes usando ... 

Y Otros lenguajes tambien tenemos cosas parecidas: 

  • C++20: std::span<T> cumple un rol casi idéntico a Span<T> de C#, y también es zero-copy.
  • Python: memoryview permite trabajar con buffers sin copiar, aunque menos seguro.
  • Java: No tiene slices como tal, pero ByteBuffer puede simularlos.
  • Nim, Zig, D: Todos ofrecen slices como vistas eficientes sobre datos.


En la práctica, estas estructuras son fundamentales para escribir código eficiente, especialmente en procesamiento de datos, parsers, sistemas embebidos o cualquier aplicación donde el rendimiento importa.  


lunes, 5 de mayo de 2025

Span en C#: Acceso seguro y eficiente a la memoria


A partir de C# 7.2 y .NET Core 2.1, Microsoft introdujo una de las herramientas más poderosas para manipular memoria sin sacrificar seguridad: Span<T>. Esta estructura permite trabajar con porciones contiguas de memoria (arrays, segmentos de strings, buffers, etc.) de forma eficiente, sin generar asignaciones en el heap ni usar punteros directamente.

Span<T> es una estructura stack-only que representa una ventana mutable sobre un bloque contiguo de memoria. Podés usarlo para acceder, cortar o modificar datos de arrays, slices de strings, buffers nativos, y más, sin necesidad de copiar datos.

Veamos un ejemplo: 


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

Span<int> slice = numbers.AsSpan(1, 3); // Contiene 2, 3, 4


slice[0] = 42;

Console.WriteLine(numbers[1]); // Muestra 42 (modificó el array original)


¿Por qué usar Span<T>?

  • Evita copias de memoria innecesarias
  • No genera asignaciones en el heap 
  • Mejora la performance en procesamiento de strings, buffers y arrays  
  • Ofrece seguridad de tipos y bounds-checking
  • Solo puede usarse dentro del stack (no puede almacenarse en campos de clase)


Limitaciones:

  • No puede usarse en métodos async o iteradores (async, yield return)  
  • No puede almacenarse en campos de clase o como parte de objetos del heap
  • Si necesitás algo similar pero heap-safe, podés usar Memory<T>


Veamos un ejemplo de string: 


ReadOnlySpan<char> span = "Hola Mundo".AsSpan(5);

Console.WriteLine(span.ToString()); // Mundo


Esto es ideal para parsear strings sin crear substrings intermedias.

Span<T> es una herramienta fundamental si querés escribir código de alto rendimiento en .NET. Es ideal para manipular datos binarios, strings o buffers, con el mínimo impacto en el garbage collector. Aunque tiene limitaciones (no se puede escapar del stack), su potencia compensa con creces en escenarios críticos de performance.

sábado, 3 de mayo de 2025

¿Qué significa Nullable en el archivo .csproj de C#?


Desde C# 8, el lenguaje introdujo el análisis de nulabilidad (nullable reference types), una herramienta poderosa para ayudarte a detectar posibles null en tiempo de compilación. Esta funcionalidad se activa o desactiva con la propiedad <Nullable> en el archivo .csproj.

Cuando está activado, el compilador trata los tipos de referencia como no anulables por defecto, a menos que explícitamente los marques con ?.

Veamos un ejemplo: 


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>

    <TargetFramework>net8.0</TargetFramework>

    <Nullable>enable</Nullable>

  </PropertyGroup>

</Project>


Y en el projecto: 


string name = null; // Warning: posible asignación nula

string? nickname = null; // Permitido


con Nullable deshabilitado:


string name = null; // No hay advertencias, aunque puede fallar en runtime


Activar Nullable te permite:

  • Recibir advertencias si estás usando variables que podrían ser null.
  • Hacer que tus APIs sean más expresivas (string? vs string).
  • Reducir errores en tiempo de ejecución por NullReferenceException.
  • Trabajar mejor con herramientas de análisis estático.


Usar <Nullable>enable</Nullable> en tus proyectos de C# modernos es una excelente práctica. Aumenta la robustez del código y te da más control sobre los errores relacionados con null, una de las fuentes más comunes de fallos en producción.

viernes, 2 de mayo de 2025

unsafe acceso directo a la memoria en C#


C# es un lenguaje seguro por defecto, lo que significa que evita errores comunes como accesos ilegales a memoria. Sin embargo, hay ocasiones en las que necesitamos un control más bajo nivel, como en interoperabilidad con código nativo o para optimizaciones de rendimiento. Para eso existe la palabra clave unsafe

La palabra clave unsafe en C# habilita el uso de punteros, acceso directo a memoria y operaciones que normalmente están fuera del alcance del entorno de ejecución administrado del .NET runtime. En otras palabras, permite usar características similares a C/C++, con sus pros y contras.

Para usarlo, debés marcar bloques, métodos o estructuras con la palabra clave unsafe. También tenés que habilitarlo en el proyecto.

En el `.csproj`:


<PropertyGroup>

  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

</PropertyGroup>


Veamos un ejemplo: 


unsafe

{

    int valor = 42;

    int* puntero = &valor;


    Console.WriteLine(*puntero); // Imprime 42

}


Cuando queremos apuntar a arrays o strings, que son gestionados por el Garbage Collector, necesitamos usar fixed para evitar que el objeto se mueva en memoria:


unsafe

{

    int[] numeros = { 10, 20, 30 };

    fixed (int* p = numeros)

    {

        Console.WriteLine(p[1]); // Imprime 20

    }

}



Que podemos hacer con unsafe?

  • Declaración y uso de punteros (`*`, `&`)
  • Indexación de punteros (`p[i]`)
  • Aritmética de punteros (`p++`, `p + 1`, etc.)
  • Conversión entre tipos de punteros
  • Uso de `sizeof(T)` para tipos primitivos


Pero ¿Cuándo usamos unsafe?

  • Interop con librerías nativas (C, C++, DLLs).
  • Manipulación avanzada de memoria.
  • Procesamiento de imágenes o buffers donde la performance es crítica.
  • Serialización binaria de alto rendimiento.

Como se pueden imaginar esto es tan util como peligroso, que es lo que puede salir mal?

  • Podemos introducir errores difíciles de depurar (como corrupción de memoria).
  • Desactiva algunas protecciones del CLR.
  • El código unsafe no se ejecuta en entornos con restricciones de seguridad (como ciertos sandboxes).
  • No es portable entre arquitecturas de forma garantizada.


Dentro de unsafe, podemos usar sizeof` para obtener el tamaño de tipos primitivos:


unsafe

{

    Console.WriteLine(sizeof(int));   // 4

    Console.WriteLine(sizeof(byte));  // 1

}


También podés usarlo con nint, nuint, float, double, etc.

Si queremos evitar unsafe pero aún así trabajar con memoria de forma eficiente:



El modo unsafe en C# te da acceso a un poder inmenso, pero con gran responsabilidad. Es una herramienta útil para situaciones específicas donde el rendimiento o la interoperabilidad lo justifican, pero debe usarse con precaución y conocimiento.


¿Qué es ImplicitUsings en C# y por qué es útil?


Con la llegada de .NET 6 y la idea de simplificar el código, Microsoft introdujo una nueva funcionalidad que puede ahorrarte varias líneas repetitivas en los archivos .cs: los usings implícitos, habilitados con la propiedad <ImplicitUsings>.

Cuando activás esta propiedad en el archivo .csproj, el compilador de C# agrega automáticamente un conjunto de using comunes a todos los archivos del proyecto, sin que tengas que escribirlos vos mismo.

Por ejemplo: 

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>

    <TargetFramework>net8.0</TargetFramework>

    <ImplicitUsings>enable</ImplicitUsings>

  </PropertyGroup>

</Project>


Gracias a esto, podés usar tipos como List<string>, Task, HttpClient, etc., directamente, sin declarar explícitamente:


using System.Collections.Generic;

using System.Threading.Tasks;

using System.Net.Http;


¿Qué usings incluye? Depende del tipo de proyecto (console, ASP.NET, etc.), pero algunos de los más comunes son:

  • System
  • System.Collections.Generic
  • System.IO
  • System.Linq
  • System.Net.Http
  • System.Threading.Tasks
  • Y muchos más según el SDK usado


Si querés ver la lista exacta, podés mirar el archivo generado: obj/YourProject.GlobalUsings.g.cs

<ImplicitUsings> es una pequeña gran herramienta que simplifica la escritura de código en C#, especialmente útil en proyectos nuevos. Al eliminar la necesidad de repetir los mismos using una y otra vez, te permite concentrarte en lo que realmente importa: la lógica de tu aplicación.

jueves, 1 de mayo de 2025

Clases, Métodos, Propiedades e Indexadores Parciales en C#


En C#, el modificador partial no se limita solo a clases. También puede aplicarse a métodos, propiedades y hasta indexadores.  

Esta característica permite dividir y extender la definición de estos elementos, haciendo que el código sea más limpio, modular y mantenible.

Una clase parcial es aquella que puede ser dividida en múltiples archivos.  

Cada fragmento de la clase debe usar el modificador partial.

Veamos un ejemplo:


// Archivo Persona_Datos.cs

public partial class Persona

{

    public string Nombre { get; set; }

}


// Archivo Persona_Operaciones.cs

public partial class Persona

{

    public string ObtenerNombreCompleto() => Nombre;

}


Al compilar, el compilador junta todas las partes como si estuvieran en un único archivo.

Un método parcial (partial void) permite declarar un método que puede (o no) ser implementado en otra parte de la clase.  

Si no se implementa, el compilador simplemente lo ignora, como si nunca hubiera existido.

Vamos con el ejemplo: 


// Declaración

public partial class Persona

{

    partial void ValidarNombre();

}


// Implementación opcional

public partial class Persona

{

    partial void ValidarNombre()

    {

        if (string.IsNullOrEmpty(Nombre))

        {

            Console.WriteLine("Nombre inválido");

        }

    }

}


  • Los métodos parciales siempre deben ser void.
  • No pueden tener modificadores de acceso (`public`, `private`, etc.).
  • No pueden ser virtuales ni static ni async.


Propiedades Parciales (desde C# 12)


Desde C# 12, se pueden crear propiedades parciales, permitiendo que la lógica de getters y setters sea definida en distintas partes del código.


public partial class Persona

{

    public partial string Nombre { get; set; }

}


// En otro archivo:

public partial class Persona

{

    public partial string Nombre

    {

        get => _nombre;

        set => _nombre = value.Trim();

    }


    private string _nombre;

}


Con esta característica se puede separar la definición de una propiedad de su comportamiento, facilitando la generación automática de propiedades o su personalización.

Un indexador parcial permite dividir la definición de un indexador (this[...]) en varias partes.

Por ejemplo:


public partial class MiColeccion

{

    public partial string this[int index] { get; set; }

}


// En otro archivo:

public partial class MiColeccion

{

    private string[] _datos = new string[10];


    public partial string this[int index]

    {

        get => _datos[index];

        set => _datos[index] = value;

    }

}

Partial es muy importante porque: 

  • Separar código generado automáticamente del código personalizado.
  • Mejorar la legibilidad y mantenimiento en proyectos grandes.
  • Permitir que distintos desarrolladores trabajen simultáneamente en distintas partes de una clase.
  • Facilitar extensiones futuras sin modificar directamente el código base.

El soporte de C# para clases, métodos, propiedades e indexadores parciales te da un gran poder para modularizar tu código de forma clara y ordenada.  

Esta es una característica que, bien aprovechada, puede hacer una gran diferencia en proyectos de cualquier tamaño.

martes, 29 de abril de 2025

Parámetros por Referencia en C#: ref, out, in y Punteros (unsafe)


Cuando programás en C#, tenés varias formas de pasar argumentos a un método: por valor (el comportamiento predeterminado) o por referencia. Esta última opción es muy útil cuando necesitás que el método pueda modificar directamente la variable original, sin copiarla.

En este post te cuento las distintas formas de hacerlo: ref, out, in, y punteros (unsafe). Además, te muestro cómo declararlos, cuándo conviene usarlos, y qué diferencias tienen entre sí.

ref: Lectura y escritura

Con ref, el método puede leer y modificar la variable pasada. Pero el valor debe estar inicializado antes de pasarla.

void SumarUno(ref int numero)
{
    numero += 1;
}

int x = 5;
SumarUno(ref x);  // x ahora vale 6


out: Solo escritura

Con out, el método debe asignar un valor. La variable no necesita estar inicializada previamente.

void ObtenerDoble(out int resultado)
{
    resultado = 84;
}

int r;
ObtenerDoble(out r);  // r ahora vale 84

Ideal cuando querés devolver más de un valor desde un método.


in: Solo lectura

Con in, pasás una variable por referencia, pero solo para lectura. Sirve especialmente con structs grandes, para evitar la copia de memoria.


void Mostrar(in int numero)
{
    Console.WriteLine(numero);
}

int y = 10;
Mostrar(in y);  // y no se puede modificar dentro del método


Punteros (unsafe): Bajo nivel

Si necesitás controlar la memoria directamente, podés usar punteros en código unsafe.

unsafe void Incrementar(int* ptr)
{
    *ptr += 1;
}

unsafe
{
    int z = 7;
    Incrementar(&z);  // z ahora vale 8
}


Declaración rápida

// ref
void Modificar(ref int x)

// out
void Inicializar(out int x)

// in
void SoloLeer(in int x)

// unsafe pointer
unsafe void Modificar(int* x)


Comparativa rápida

Keyword ¿Inicialización previa? ¿Se puede leer? ¿Se puede escribir? Uso típico
ref Modificar valor
out Devolver datos
in Structs grandes
* Bajo nivel


Ejemplos útiles

// struct con ref
struct Punto { public int X, Y; }

void Mover(ref Punto p) => p.X += 10;

// out con TryParse
bool TryParseNumero(string s, out int resultado)
{
    return int.TryParse(s, out resultado);
}

// in con cálculo
void MostrarDistancia(in Punto p)
{
    Console.WriteLine($"Distancia: {Math.Sqrt(p.X * p.X + p.Y * p.Y)}");
}

// unsafe con array
unsafe void Duplicar(int* arr, int n)
{
    for (int i = 0; i < n; i++) arr[i] *= 2;
}


C# te da muchas formas de pasar datos, y conocer ref, out, in y los punteros te permite optimizar tu código, mejorar la performance, y diseñar APIs más expresivas. Cada palabra clave tiene su contexto ideal, y entenderlas bien te va a dar más poder sobre lo que tu código realmente hace.


sábado, 26 de abril de 2025

Nuevas características de C# 13


C# 13, junto con .NET 9, trae una serie de mejoras que buscan simplificar la escritura de código, mejorar el rendimiento y ofrecer mayor flexibilidad a los desarrolladores. Veamos que trajo:

Colecciones params mejoradas

Ahora, el modificador params se puede aplicar a tipos que admiten inicialización mediante expresiones de colección, no solo a arreglos. Esto permite una mayor flexibilidad al definir métodos que aceptan un número variable de argumentos.


public void EscribirNombres(params List<string> nombres)

    => Console.WriteLine(string.Join(", ", nombres));


Nuevo tipo de bloqueo: System.Threading.Lock

Se introduce un nuevo tipo de bloqueo que mejora la sincronización de subprocesos. A diferencia del uso tradicional de lock con objetos, System.Threading.Lock proporciona una API más clara y segura para manejar exclusiones mutuas.


var myLock = new Lock();

using (myLock.EnterScope())

{

    // Código protegido

}


Nueva secuencia de escape: \e

Se añade la secuencia de escape \e para representar el carácter de escape ASCII (U+001B). Anteriormente, se utilizaban \u001b o \x1b, pero esta nueva notación es más clara y evita ambigüedades.


Tipos naturales de grupo de métodos

Se mejora la inferencia de tipos al trabajar con grupos de métodos, permitiendo que el compilador determine de manera más precisa el tipo adecuado en contextos donde se utilizan delegados o expresiones lambda.


Propiedades e indexadores parciales

Al igual que los métodos parciales, ahora es posible declarar propiedades e indexadores parciales. Esto facilita la separación de la declaración y la implementación, especialmente útil al trabajar con generadores de código.


partial class MiClase

{

    public partial int MiPropiedad { get; set; }

}


Acceso implícito a indexadores en inicializadores de objetos

Se permite el uso de índices implícitos, como [^1], dentro de inicializadores de objetos, lo que simplifica la inicialización de colecciones y estructuras de datos complejas.


var miArreglo = new int[5] { [^1] = 10 };


Soporte para ref struct en interfaces y genéricos

Los tipos ref struct ahora pueden implementar interfaces y ser utilizados como argumentos de tipo en genéricos, ampliando su aplicabilidad en escenarios de alto rendimiento y manipulación de memoria.


Variables ref y contextos unsafe en iteradores y métodos asincrónicos

Se habilita el uso de variables ref locales y contextos unsafe dentro de métodos asincrónicos y iteradores, siempre que se cumplan ciertas restricciones para garantizar la seguridad del código.


Atributo OverloadResolutionPriority

Se introduce el atributo OverloadResolutionPriority que permite a los desarrolladores especificar la prioridad de resolución de sobrecargas, facilitando la evolución de las API sin romper compatibilidad con versiones anteriores.

Estas mejoras reflejan el compromiso continuo de la comunidad de C# por evolucionar el lenguaje, haciéndolo más potente y expresivo. 


Dejo link: https://developers.redhat.com/articles/2025/04/16/c-13-new-features#

viernes, 21 de marzo de 2025

Número variable de argumentos en C#


En C#, la palabra clave params se usa para permitir que un método acepte un número variable de argumentos del mismo tipo sin necesidad de definir múltiples sobrecargas.  

¿Cómo funciona?  

  • Solo puede haber un parámetro params por método, y debe ser el último parámetro en la lista.  
  • El argumento pasado puede ser una lista de valores separados por comas o un array del tipo especificado.  

Veamos un ejemplo: 


using System;


class Program

{

    static void Main()

    {

        ImprimirNumeros(1, 2, 3, 4, 5);

        ImprimirNumeros(10, 20);

        ImprimirNumeros(); // No pasa nada, la lista puede estar vacía

    }


    static void ImprimirNumeros(params int[] numeros)

    {

        foreach (var num in numeros)

        {

            Console.Write(num + " ");

        }

        Console.WriteLine();

    }

}

La salida va a ser:

1 2 3 4 5  

10 20  


También puedes pasar un array en lugar de valores separados por comas:  


int[] valores = { 100, 200, 300 };

ImprimirNumeros(valores);



Puedes mezclar params con otros parámetros, pero debe estar al final:  


static void Saludar(string mensaje, params string[] nombres)

{

    foreach (var nombre in nombres)

    {

        Console.WriteLine($"{mensaje}, {nombre}!");

    }

}


// Llamadas válidas:

Saludar("Hola", "Juan", "Ana", "Luis");  

Saludar("Bienvenido", "Carlos");  


Y la salida va a ser: 


Hola, Juan!  

Hola, Ana!  

Hola, Luis!  

Bienvenido, Carlos!  


¿Cuándo usar params?  

  • Cuando quieres un método flexible sin definir múltiples sobrecargas.  
  • Para métodos como Console.WriteLine(), que pueden recibir cualquier cantidad de argumentos.  
  • Para situaciones en las que puede haber 0 o más parámetros opcionales sin usar List<T> o IEnumerable<T>.  


domingo, 9 de febrero de 2025

Interceptores en C#


Con la llegada de C# 12, una de las características más interesantes introducidas es la capacidad de interceptar llamadas a métodos mediante Interceptores. Esta funcionalidad permite modificar o analizar la ejecución de un método antes o después de su invocación, lo que resulta útil para aspectos como logging, validación, caching y manejo de excepciones.

Los interceptores son una característica que permite redirigir llamadas a métodos a una lógica personalizada en tiempo de compilación. Esto brinda un alto nivel de control sin necesidad de modificar el código fuente original.

Para implementar interceptores en C#, se usa el atributo [InterceptsLocation], que indica que un método debe reemplazar otro en una ubicación específica del código.

Supongamos que tenemos un método que realiza una operación matemática simple:


public static class MathOperations

{

    public static int Add(int a, int b) => a + b;

}


Podemos definir un interceptor que intercepte esta llamada y agregue una funcionalidad adicional, como logging:



using System.Runtime.CompilerServices;


public static class MathInterceptor

{

    [InterceptsLocation("MathOperations.cs", line: 5, column: 5)]

    public static int Add(int a, int b)

    {

        Console.WriteLine($"Interceptando llamada a Add({a}, {b})");

        return a + b;

    }

}


Para tener en cuenta: 

  • Los interceptores funcionan en tiempo de compilación y no en ejecución.
  • Solo pueden ser usados en métodos con ubicaciones explícitas dentro del código fuente.
  • Requieren compatibilidad con la infraestructura de compilación adecuada.


En mi opinión es una forma de programación por aspecto o se podria implementar con esta nueva feature. 


sábado, 18 de enero de 2025

El Operador |> de Elixir y sus equivalentes en otros lenguajes


En Elixir, el operador |> pasa el resultado de una expresión como el primer argumento de la siguiente función. Ya lo explicamos en el post anterior. 


" hello "

|> String.trim()

|> String.upcase()

Resultado: "HELLO"


Este diseño promueve una lectura fluida del código, eliminando la necesidad de paréntesis anidados.


F#, un lenguaje funcional inspirado en ML, también tiene un operador pipe |> con un propósito similar al de Elixir.


" hello "

|> String.trim

|> String.uppercase


El operador en F# permite que el flujo de datos sea explícito, facilitando la composición de funciones.


Python no tiene un operador pipe nativo, pero existen bibliotecas que lo emulan, como `pipe` o `toolz`. Sin embargo, sin bibliotecas adicionales, puedes lograr algo similar con reduce:


from functools import reduce


data = " hello "

result = reduce(lambda acc, fn: fn(acc), [str.strip, str.upper], data)

print(result)  # HELLO


Con una biblioteca como pipe:


from pipe import Pipe


result = " hello " | Pipe(str.strip) | Pipe(str.upper)

print(result)  # HELLO


JavaScript aún no tiene un operador pipe oficial, pero hay una propuesta en desarrollo en el comité TC39 (etapa 2 al momento de escribir). Con esta propuesta, el pipe se usa de la siguiente manera:


" hello "

  |> (x => x.trim())

  |> (x => x.toUpperCase());


Por ahora, puedes emularlo con funciones:


const pipeline = (...fns) => x => fns.reduce((v, f) => f(v), x);


const result = pipeline(

  x => x.trim(),

  x => x.toUpperCase()

)(" hello ");

console.log(result); // HELLO


Scala no tiene un operador pipe nativo, pero es posible definir uno:


implicit class PipeOps[T](val value: T) extends AnyVal {

  def |>[R](f: T => R): R = f(value)

}


val result = " hello "

  |> (_.trim)

  |> (_.toUpperCase)

println(result) // HELLO


En C#, aunque no existe un operador pipe, los métodos de extensión de LINQ se comportan de manera similar:


string result = " hello "

    .Trim()

    .ToUpper();

Console.WriteLine(result); // HELLO


El concepto detrás del operador pipe (`|>`) es universal: facilita la composición de funciones y mejora la legibilidad. Aunque su implementación varía entre lenguajes, su propósito sigue siendo el mismo: transformar datos paso a paso de manera clara y concisa.


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.