Antes de empezar lee la parte 1. No te adelantes.
- console: Aplicación de consola.
- classlib: Biblioteca de clases.
- web: Aplicación web ASP.NET Core.
- mvc: Aplicación ASP.NET Core MVC.
- blazorserver: Aplicación Blazor Server.
Empecemos por el principio. El comando `dotnet` es la herramienta de línea de comandos que viene con el SDK de .NET y que permite a los desarrolladores realizar una amplia gama de tareas relacionadas con la creación, compilación, depuración y despliegue de aplicaciones .NET. Es la interfaz principal para interactuar con el runtime y las bibliotecas de .NET, así como para gestionar paquetes NuGet, herramientas y otros componentes.
dotnet --version
Este comando muestra la versión del SDK de .NET instalado en tu máquina, lo que es útil para verificar rápidamente qué versión estás utilizando.
El comando `dotnet` se utiliza para una variedad de tareas esenciales en el desarrollo de aplicaciones .NET:
Para crear y ejecutar una simple aplicación de consola, usarías:
dotnet new console -n MyApp
cd MyApp
dotnet run
Este conjunto de comandos crea una nueva aplicación de consola, navega al directorio del proyecto, y ejecuta la aplicación.
Como es de esperar, para usar el comando `dotnet`, necesitas tener instalado el SDK de .NET en tu máquina. El SDK incluye todo lo necesario para desarrollar aplicaciones con .NET, incluyendo el runtime y la herramienta `dotnet`.
Los pasos para instalar el sdk son sencillos:
Bajar el instalador de https://dotnet.microsoft.com/download. Tenes que elegir la plataforma que usas (Windows, macOS, Linux).
Una vez que tengas el instalador doble click y le das next todas las veces que necesite (sin miedo al exito)
Luego abris una terminal o línea de comandos y ejecuta:
dotnet --version
Si el comando devuelve un número de versión, la instalación fue exitosa.
Este SDK es necesario no solo para compilar y ejecutar aplicaciones, sino también para utilizar todas las funcionalidades avanzadas que el comando `dotnet` ofrece.
La idea es correr C# como un lenguaje script y esto lo podemos hacer con Roslyn.
Primero, instalamos las dependencias:
dotnet add package Microsoft.CodeAnalysis.Scripting --version 4.9.2
dotnet add package Microsoft.CodeAnalysis.CSharp.Scripting --version 4.9.2
Y con eso ya estamos, podemos hacer esto:
using System;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
class Program
{
static async System.Threading.Tasks.Task Main(string[] args)
{
try
{
// Creamos el script
string code = @"
using System;
public class MyClass
{
public void MyMethod()
{
Console.WriteLine(""Hello from C#!"");
}
}";
// Creamos las opciones (usamos las por defecto)
ScriptOptions options = ScriptOptions.Default;
// Creamos el motor que ejecuta el script
var script = CSharpScript.Create(code, options);
// corremos el script
var result = await script.RunAsync();
// Y chequeamos si hubo error.
if (result.Exception != null)
{
Console.WriteLine("Script execution failed: " + result.Exception.Message);
}
}
catch (Exception ex)
{
Console.WriteLine("Error executing script: " + ex.Message);
}
}
}
Vamos con un ejemplo:
Primero agregamos la dependencia:
dotnet add package Microsoft.ClearScript --version 7.4.5
Y bueno, ahora podemos ejecutar javascript por ejemplo:
using Microsoft.ClearScript;
using Microsoft.ClearScript.V8;
public class Example
{
static void Main()
{
// Create a new V8ScriptEngine
using var engine = new V8ScriptEngine();
try
{
// Execute JavaScript code
engine.Execute("var x = 10; var y = 20; var z = x + y;");
// Get the value of a JavaScript variable
var zValue = engine.Script.z;
Console.WriteLine("The value of z is: " + zValue);
}
catch (ScriptEngineException ex)
{
Console.WriteLine("Error executing JavaScript: " + ex.Message);
}
}
}
Y si todo fue bien el resultado es:
The value of z is: 30
Y listo!
Dejo link: https://microsoft.github.io/ClearScript/
Utiliza un patrón var para hacer coincidir cualquier expresión, incluido nulo, y asigna su resultado a una nueva variable local, como muestra el siguiente ejemplo:
static bool IsAcceptable(int id, int absLimit) =>
SimulateDataFetch(id) is var results
&& results.Min() >= -absLimit
&& results.Max() <= absLimit;
static int[] SimulateDataFetch(int id)
{
var rand = new Random();
return Enumerable
.Range(start: 0, count: 5)
.Select(s => rand.Next(minValue: -10, maxValue: 11))
.ToArray();
}
Un patrón var es útil cuando necesitamos una variable temporal dentro de una expresión booleana para contener el resultado de cálculos intermedios. También podemos usar un patrón var cuando necesitamos realizar más comprobaciones cuando las mayúsculas y minúsculas protegen una expresión o declaración de cambio, como muestra el siguiente ejemplo:
public record Point(int X, int Y);
static Point Transform(Point point) => point switch
{
var (x, y) when x < y => new Point(-x, y),
var (x, y) when x > y => new Point(x, -y),
var (x, y) => new Point(x, y),
};
static void TestTransform()
{
Console.WriteLine(Transform(new Point(1, 2))); // output: Point { X = -1, Y = 2 }
Console.WriteLine(Transform(new Point(5, 2))); // output: Point { X = 5, Y = -2 }
}
En el ejemplo anterior, el patrón var (x, y) es equivalente a un patrón posicional (var x, var y).
En un patrón var, el tipo de una variable declarada es el tipo de tiempo de compilación de la expresión que se compara con el patrón.
Patrón descartar
Utiliza un patrón de descarte _ para hacer coincidir cualquier expresión, incluido nulo, como muestra el siguiente ejemplo:
Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday)); // output: 5.0
Console.WriteLine(GetDiscountInPercent(null)); // output: 0.0
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10)); // output: 0.0
static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
DayOfWeek.Monday => 0.5m,
DayOfWeek.Tuesday => 12.5m,
DayOfWeek.Wednesday => 7.5m,
DayOfWeek.Thursday => 12.5m,
DayOfWeek.Friday => 5.0m,
DayOfWeek.Saturday => 2.5m,
DayOfWeek.Sunday => 2.0m,
_ => 0.0m,
};
En el ejemplo anterior, se usa un patrón de descarte para controlar valores nulos y enteros que no tienen el miembro correspondiente de la enumeración DayOfWeek. Eso garantiza que una expresión de cambio en el ejemplo maneje todos los valores de entrada posibles. Si no usa un patrón de descarte en una expresión de cambio y ninguno de los patrones de la expresión coincide con una entrada, el tiempo de ejecución genera una excepción. El compilador genera una advertencia si una expresión de cambio no maneja todos los valores de entrada posibles.
Un patrón de descarte no puede ser un patrón en una expresión is o una declaración de cambio. En esos casos, para hacer coincidir cualquier expresión, podemos usar un patrón var con un descarte: var _.
Patrón entre paréntesis
A partir de C# 9.0, podemos poner paréntesis alrededor de cualquier patrón. Por lo general, hace eso para enfatizar o cambiar la precedencia en patrones lógicos, como muestra el siguiente ejemplo:
if (input is not (float or double))
{
return;
}
Patrones de lista
A partir de C# 11, puede hacer coincidir una matriz o una lista con una secuencia de patrones, como muestra el siguiente ejemplo:
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers is [1, 2, 3]); // True
Console.WriteLine(numbers is [1, 2, 4]); // False
Console.WriteLine(numbers is [1, 2, 3, 4]); // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]); // True
Como muestra el ejemplo anterior, un patrón de lista coincide cuando cada patrón anidado coincide con el elemento correspondiente de una secuencia de entrada. Puede usar cualquier patrón dentro de un patrón de lista. Para hacer coincidir cualquier elemento, use el patrón de descarte o, si también desea capturar el elemento, el patrón var, como muestra el siguiente ejemplo:
List<int> numbers = new() { 1, 2, 3 };
if (numbers is [var first, _, _])
{
Console.WriteLine($"The first element of a three-item list is {first}.");
}
// Output:
// The first element of a three-item list is 1.
Los ejemplos anteriores comparan una secuencia de entrada completa con un patrón de lista. Para hacer coincidir los elementos solo al principio o al final de una secuencia de entrada, podemos usar el patrón de división .. dentro de un patrón de lista, como muestra el siguiente ejemplo:
Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]); // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]); // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]); // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]); // False
Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]); // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]); // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]); // True
Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]); // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]); // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]); // False
Un patrón de división coincide con cero o más elementos. Puede usar como máximo un patrón de división en un patrón de lista.
También puede anidar un subpatrón dentro de un patrón de división, como muestra el siguiente ejemplo:
void MatchMessage(string message)
{
var result = message is ['a' or 'A', .. var s, 'a' or 'A']
? $"Message {message} matches; inner part is {s}."
: $"Message {message} doesn't match.";
Console.WriteLine(result);
}
MatchMessage("aBBA"); // output: Message aBBA matches; inner part is BB.
MatchMessage("apron"); // output: Message apron doesn't match.
void Validate(int[] numbers)
{
var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
Console.WriteLine(result);
}
Validate(new[] { -1, 0, 1 }); // output: not valid
Validate(new[] { -1, 0, 0, 1 }); // output: valid
Utiliza un patrón posicional para deconstruir un resultado de expresión y hacer coincidir los valores resultantes con los patrones anidados correspondientes, como muestra el siguiente ejemplo:
public readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}
static string Classify(Point point) => point switch
{
(0, 0) => "Origin",
(1, 0) => "positive X basis end",
(0, 1) => "positive Y basis end",
_ => "Just a point",
};
En el ejemplo anterior, el tipo de una expresión contiene el método Deconstruct, que se utiliza para deconstruir el resultado de una expresión. También puede hacer coincidir expresiones de tipos de tupla con patrones posicionales. De esa manera, puede hacer coincidir varias entradas con varios patrones, como muestra el siguiente ejemplo:
static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)
=> (groupSize, visitDate.DayOfWeek) switch
{
(<= 0, _) => throw new ArgumentException("Group size must be positive."),
(_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
(>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
(>= 10, DayOfWeek.Monday) => 30.0m,
(>= 5 and < 10, _) => 12.0m,
(>= 10, _) => 15.0m,
_ => 0.0m,
};
El ejemplo anterior usa patrones relacionales y lógicos, que están disponibles en C# 9.0 y versiones posteriores.
Se puede usar los nombres de los elementos de tupla y los parámetros de Deconstrucción en un patrón posicional, como muestra el siguiente ejemplo:
var numbers = new List<int> { 1, 2, 3 };
if (SumAndCount(numbers) is (Sum: var sum, Count: > 0))
{
Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}"); // output: Sum of [1 2 3] is 6
}
static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{
int sum = 0;
int count = 0;
foreach (int number in numbers)
{
sum += number;
count++;
}
return (sum, count);
}
También puede extender un patrón posicional de cualquiera de las siguientes maneras:
public record Point2D(int X, int Y);
public record Point3D(int X, int Y, int Z);
static string PrintIfAllCoordinatesArePositive(object point) => point switch
{
Point2D (> 0, > 0) p => p.ToString(),
Point3D (> 0, > 0, > 0) p => p.ToString(),
_ => string.Empty,
};
El ejemplo anterior usa registros posicionales que proporcionan implícitamente el método Deconstruct.
public record WeightedPoint(int X, int Y)
{
public double Weight { get; set; }
}
static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p)
{
// ..
}
Un patrón posicional es un patrón recursivo. Es decir, puede utilizar cualquier patrón como patrón anidado.
A partir de C# 9.0, utiliza los combinadores de patrones not, and y or para crear los siguientes patrones lógicos:
if (input is not null)
{
// ...
}
Console.WriteLine(Classify(13)); // output: HighConsole.WriteLine(Classify(-100)); // output: Too lowConsole.WriteLine(Classify(5.7)); // output: Acceptablestatic string Classify(double measurement) => measurement switch{< -40.0 => "Too low",>= -40.0 and < 0 => "Low",>= 0 and < 10.0 => "Acceptable",>= 10.0 and < 20.0 => "High",>= 20.0 => "Too high",double.NaN => "Unknown",};
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19))); // output: winter
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9))); // output: autumn
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11))); // output: spring
static string GetCalendarSeason(DateTime date) => date.Month switch
{
3 or 4 or 5 => "spring",
6 or 7 or 8 => "summer",
9 or 10 or 11 => "autumn",
12 or 1 or 2 => "winter",
_ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
};
Como muestra el ejemplo anterior, puede utilizar repetidamente los combinadores de patrones en un patrón.
Precedencia y orden de verificación
La siguiente lista ordena los combinadores de patrones comenzando desde la precedencia más alta hasta la más baja:
Para especificar explícitamente la precedencia, podemos usar paréntesis, como muestra el siguiente ejemplo:
static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
Patrón de propiedad
Se utiliza un patrón de propiedad para hacer coincidir las propiedades o campos de una expresión con patrones anidados, como muestra el siguiente ejemplo:
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };
Un patrón de propiedad coincide con una expresión cuando el resultado de una expresión no es nulo y cada patrón anidado coincide con la propiedad o el campo correspondiente del resultado de la expresión.
También puede agregar una verificación de tipo en tiempo de ejecución y una declaración de variable a un patrón de propiedad, como muestra el siguiente ejemplo:
Console.WriteLine(TakeFive("Hello, world!")); // output: Hello
Console.WriteLine(TakeFive("Hi!")); // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' })); // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' })); // output: abc
static string TakeFive(object input) => input switch
{
string { Length: >= 5 } s => s.Substring(0, 5),
string s => s,
ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
ICollection<char> symbols => new string(symbols.ToArray()),
null => throw new ArgumentNullException(nameof(input)),
_ => throw new ArgumentException("Not supported input type."),
};
Un patrón de propiedad es un patrón recursivo. Es decir, puede utilizar cualquier patrón como patrón anidado. Se puede utilizar un patrón de propiedad para hacer coincidir partes de datos con patrones anidados, como muestra el siguiente ejemplo:
public record Point(int X, int Y);
public record Segment(Point Start, Point End);
static bool IsAnyEndOnXAxis(Segment segment) =>
segment is { Start: { Y: 0 } } or { End: { Y: 0 } };
El ejemplo anterior usa dos funciones disponibles en C# 9.0 y versiones posteriores: o combinador de patrones y tipos de registro.
A partir de C# 10, puede hacer referencia a propiedades o campos anidados dentro de un patrón de propiedad. Esta capacidad se conoce como patrón de propiedad extendida. Por ejemplo, puede refactorizar el método del ejemplo anterior en el siguiente código equivalente:
static bool IsAnyEndOnXAxis(Segment segment) =>
segment is { Start.Y: 0 } or { End.Y: 0 };
Utiliza un patrón constante para probar si el resultado de una expresión es igual a una constante especificada, como muestra el siguiente ejemplo:
public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
1 => 12.0m,
2 => 20.0m,
3 => 27.0m,
4 => 32.0m,
0 => 0.0m,
_ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};
En un patrón constante, se puede usar cualquier expresión constante, como:
La expresión debe ser un tipo que se pueda convertir al tipo constante, con una excepción: una expresión cuyo tipo sea Span<char> o ReadOnlySpan<char> se puede comparar con cadenas constantes en C# 11 y versiones posteriores.
Se puede utilizar un patrón constante para verificar si hay valores nulos, como muestra el siguiente ejemplo:
if (input is null)
{
return;
}
El compilador garantiza que no se invoca ningún operador de igualdad sobrecargado por el usuario == cuando se evalúa la expresión x es nula.
A partir de C# 9.0, puede usar un patrón de constante nulo negado para verificar si no es nulo, como muestra el siguiente ejemplo:
if (input is not null)
{
// ...
}
Patrones relacionales
A partir de C# 9.0, utiliza un patrón relacional para comparar el resultado de una expresión con una constante, como muestra el siguiente ejemplo:
Console.WriteLine(Classify(13)); // output: Too high
Console.WriteLine(Classify(double.NaN)); // output: Unknown
Console.WriteLine(Classify(2.4)); // output: Acceptable
static string Classify(double measurement) => measurement switch
{
< -4.0 => "Too low",
> 10.0 => "Too high",
double.NaN => "Unknown",
_ => "Acceptable",
};
En un patrón relacional, puede utilizar cualquiera de los operadores relacionales <, >, <= o >=. La parte derecha de un patrón relacional debe ser una expresión constante. La expresión constante puede ser de tipo entero, punto flotante, carácter o enumeración.
Para verificar si el resultado de una expresión está en un cierto rango, compárelo con una conjuntiva y un patrón, como muestra el siguiente ejemplo:
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14))); // output: spring
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19))); // output: summer
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17))); // output: winter
static string GetCalendarSeason(DateTime date) => date.Month switch
{
>= 3 and < 6 => "spring",
>= 6 and < 9 => "summer",
>= 9 and < 12 => "autumn",
12 or (>= 1 and < 3) => "winter",
_ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
};
Si el resultado de una expresión es nulo o no se convierte al tipo de una constante mediante una conversión anulable o unboxing, un patrón relacional no coincide con una expresión.
var numbers = new int[] { 10, 20, 30 };Console.WriteLine(GetSourceLabel(numbers)); // output: 1var letters = new List<char> { 'a', 'b', 'c', 'd' };Console.WriteLine(GetSourceLabel(letters)); // output: 2static int GetSourceLabel<T>(IEnumerable<T> source) => source switch{Array array => 1,ICollection<T> collection => 2,_ => 3,};En el ejemplo anterior, en la primera llamada al método GetSourceLabel, el primer patrón coincide con un valor de argumento porque el tipo de tiempo de ejecución del argumento int[] se deriva del tipo Array. En la segunda llamada al método GetSourceLabel, el tipo List<T> en tiempo de ejecución del argumento no se deriva del tipo Array pero implementa la interfaz ICollection<T>.
El siguiente ejemplo demuestra las dos últimas condiciones:int? xNullable = 7;int y = 23;object yBoxed = y;if (xNullable is int a && yBoxed is int b){Console.WriteLine(a + b); // output: 30}
Y para colmo se pueden hacer más cosas de diferentes formas, haciendo que se pierda la claridad del mejor camino para resolver las cosas. Puff ... Y sin duda C#, java, scala, kotlin, etc... son muy completos y por ende muy complejos de entender cuando usar que.
Luego de esta introducción/opinión, vamos al tema del post. Cuando utilizar estructuras, registro o clases en C#?
¿Puede el tipo de datos ser un tipo de valor? entonces es un estructura. ¿No? ¿Su tipo describe un estado similar a un valor, preferiblemente inmutable? entonces registro. Y sino clase pero podemos usar registro para:
Veamos esto con más detalle. Una estructura, una clase y un registro son tipos de datos de usuario.
Las estructuras son tipos de valor. Las clases son tipos de referencia. Los registros son por defecto tipos de referencia inmutables.
Cuando necesita algún tipo de jerarquía para describir sus tipos de datos como herencia o una estructura que apunta a otra estructura o básicamente cosas que apuntan a otras cosas, necesita un tipo de referencia.
Los registros resuelven el problema cuando desea que su tipo esté orientado a valores de forma predeterminada. Los registros son tipos de referencia pero con la semántica orientada al valor.
En conclusión,
Estructura si:
¿No? Debe ser algún tipo de referencia (clase o registro).
¿El tipo de datos encapsula algún tipo de valor complejo? ¿El valor es inmutable? ¿Lo usa en flujo unidireccional (una vía)? Vamos con registro.
¿No? Vamos con clase.
Por cierto: no te olvides que habrá registros anónimos en C# 10.0, como para ponerle más complejidad.
Otra cosa, una instancia de registro puede ser mutable si la convierte en mutable.
class Program
{
static void Main()
{
var test = new Foo("a");
Console.WriteLine(test.MutableProperty);
test.MutableProperty = 15;
Console.WriteLine(test.MutableProperty);
//test.Bar = "new string"; // will not compile
}
}
public record Foo(string Bar)
{
public double MutableProperty { get; set; } = 10.0;
}
Una asignación de un registro es una copia superficial del registro. Una copia con expresión de un registro no es ni superficial ni profunda. La copia se crea mediante un método de clonación especial emitido por el compilador de C#. Los miembros de tipo de valor se copian y encuadran. Los miembros de tipo de referencia apuntan a la misma referencia. Puede hacer una copia profunda de un registro si y solo si el registro solo tiene propiedades de tipo de valor. Cualquier propiedad de miembro de tipo de referencia de un registro se copia como una copia superficial.
Veamos este ejemplo (usando la función de nivel superior en C# 9.0):
using System.Collections.Generic;
using static System.Console;
var foo = new SomeRecord(new List<string>());
var fooAsShallowCopy = foo;
var fooAsWithCopy = foo with { }; // A syntactic sugar for new SomeRecord(foo.List);
var fooWithDifferentList = foo with { List = new List<string>() { "a", "b" } };
var differentFooWithSameList = new SomeRecord(foo.List); // This is the same like foo with { };
foo.List.Add("a");
WriteLine($"Count in foo: {foo.List.Count}"); // 1
WriteLine($"Count in fooAsShallowCopy: {fooAsShallowCopy.List.Count}"); // 1
WriteLine($"Count in fooWithDifferentList: {fooWithDifferentList.List.Count}"); // 2
WriteLine($"Count in differentFooWithSameList: {differentFooWithSameList.List.Count}"); // 1
WriteLine($"Count in fooAsWithCopy: {fooAsWithCopy.List.Count}"); // 1
WriteLine("");
WriteLine($"Equals (foo & fooAsShallowCopy): {Equals(foo, fooAsShallowCopy)}"); // True. The lists inside are the same.
WriteLine($"Equals (foo & fooWithDifferentList): {Equals(foo, fooWithDifferentList)}"); // False. The lists are different
WriteLine($"Equals (foo & differentFooWithSameList): {Equals(foo, differentFooWithSameList)}"); // True. The list are the same.
WriteLine($"Equals (foo & fooAsWithCopy): {Equals(foo, fooAsWithCopy)}"); // True. The list are the same, see below.
WriteLine($"ReferenceEquals (foo.List & fooAsShallowCopy.List): {ReferenceEquals(foo.List, fooAsShallowCopy.List)}"); // True. The records property points to the same reference.
WriteLine($"ReferenceEquals (foo.List & fooWithDifferentList.List): {ReferenceEquals(foo.List, fooWithDifferentList.List)}"); // False. The list are different instances.
WriteLine($"ReferenceEquals (foo.List & differentFooWithSameList.List): {ReferenceEquals(foo.List, differentFooWithSameList.List)}"); // True. The records property points to the same reference.
WriteLine($"ReferenceEquals (foo.List & fooAsWithCopy.List): {ReferenceEquals(foo.List, fooAsWithCopy.List)}"); // True. The records property points to the same reference.
WriteLine("");
WriteLine($"ReferenceEquals (foo & fooAsShallowCopy): {ReferenceEquals(foo, fooAsShallowCopy)}"); // True. !!! fooAsCopy is pure shallow copy of foo. !!!
WriteLine($"ReferenceEquals (foo & fooWithDifferentList): {ReferenceEquals(foo, fooWithDifferentList)}"); // False. These records are two different reference variables.
WriteLine($"ReferenceEquals (foo & differentFooWithSameList): {ReferenceEquals(foo, differentFooWithSameList)}"); // False. These records are two different reference variables and reference type property hold by these records does not matter in ReferenceEqual.
WriteLine($"ReferenceEquals (foo & fooAsWithCopy): {ReferenceEquals(foo, fooAsWithCopy)}"); // False. The same story as differentFooWithSameList.
WriteLine("");
var bar = new RecordOnlyWithValueNonMutableProperty(0);
var barAsShallowCopy = bar;
var differentBarDifferentProperty = bar with { NonMutableProperty = 1 };
var barAsWithCopy = bar with { };
WriteLine($"Equals (bar & barAsShallowCopy): {Equals(bar, barAsShallowCopy)}"); // True.
WriteLine($"Equals (bar & differentBarDifferentProperty): {Equals(bar, differentBarDifferentProperty)}"); // False. Remember, the value equality is used.
WriteLine($"Equals (bar & barAsWithCopy): {Equals(bar, barAsWithCopy)}"); // True. Remember, the value equality is used.
WriteLine($"ReferenceEquals (bar & barAsShallowCopy): {ReferenceEquals(bar, barAsShallowCopy)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (bar & differentBarDifferentProperty): {ReferenceEquals(bar, differentBarDifferentProperty)}"); // False. Operator with creates a new reference variable.
WriteLine($"ReferenceEquals (bar & barAsWithCopy): {ReferenceEquals(bar, barAsWithCopy)}"); // False. Operator with creates a new reference variable.
WriteLine("");
var fooBar = new RecordOnlyWithValueMutableProperty();
var fooBarAsShallowCopy = fooBar; // A shallow copy, the reference to bar is assigned to barAsCopy
var fooBarAsWithCopy = fooBar with { }; // A deep copy by coincidence because fooBar has only one value property which is copied into barAsDeepCopy.
WriteLine($"Equals (fooBar & fooBarAsShallowCopy): {Equals(fooBar, fooBarAsShallowCopy)}"); // True.
WriteLine($"Equals (fooBar & fooBarAsWithCopy): {Equals(fooBar, fooBarAsWithCopy)}"); // True. Remember, the value equality is used.
WriteLine($"ReferenceEquals (fooBar & fooBarAsShallowCopy): {ReferenceEquals(fooBar, fooBarAsShallowCopy)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (fooBar & fooBarAsWithCopy): {ReferenceEquals(fooBar, fooBarAsWithCopy)}"); // False. Operator with creates a new reference variable.
WriteLine("");
fooBar.MutableProperty = 2;
fooBarAsShallowCopy.MutableProperty = 3;
fooBarAsWithCopy.MutableProperty = 3;
WriteLine($"fooBar.MutableProperty = {fooBar.MutableProperty} | fooBarAsShallowCopy.MutableProperty = {fooBarAsShallowCopy.MutableProperty} | fooBarAsWithCopy.MutableProperty = {fooBarAsWithCopy.MutableProperty}"); // fooBar.MutableProperty = 3 | fooBarAsShallowCopy.MutableProperty = 3 | fooBarAsWithCopy.MutableProperty = 3
WriteLine($"Equals (fooBar & fooBarAsShallowCopy): {Equals(fooBar, fooBarAsShallowCopy)}"); // True.
WriteLine($"Equals (fooBar & fooBarAsWithCopy): {Equals(fooBar, fooBarAsWithCopy)}"); // True. Remember, the value equality is used. 3 != 4
WriteLine($"ReferenceEquals (fooBar & fooBarAsShallowCopy): {ReferenceEquals(fooBar, fooBarAsShallowCopy)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (fooBar & fooBarAsWithCopy): {ReferenceEquals(fooBar, fooBarAsWithCopy)}"); // False. Operator with creates a new reference variable.
WriteLine("");
fooBarAsWithCopy.MutableProperty = 4;
WriteLine($"fooBar.MutableProperty = {fooBar.MutableProperty} | fooBarAsShallowCopy.MutableProperty = {fooBarAsShallowCopy.MutableProperty} | fooBarAsWithCopy.MutableProperty = {fooBarAsWithCopy.MutableProperty}"); // fooBar.MutableProperty = 3 | fooBarAsShallowCopy.MutableProperty = 3 | fooBarAsWithCopy.MutableProperty = 4
WriteLine($"Equals (fooBar & fooBarAsWithCopy): {Equals(fooBar, fooBarAsWithCopy)}"); // False. Remember, the value equality is used. 3 != 4
WriteLine("");
var venom = new MixedRecord(new List<string>(), 0); // Reference/Value property, mutable non-mutable.
var eddieBrock = venom;
var carnage = venom with { };
venom.List.Add("I'm a predator.");
carnage.List.Add("All I ever wanted in this world is a carnage.");
WriteLine($"Count in venom: {venom.List.Count}"); // 2
WriteLine($"Count in eddieBrock: {eddieBrock.List.Count}"); // 2
WriteLine($"Count in carnage: {carnage.List.Count}"); // 2
WriteLine($"Equals (venom & eddieBrock): {Equals(venom, eddieBrock)}"); // True.
WriteLine($"Equals (venom & carnage): {Equals(venom, carnage)}"); // True. Value properties has the same values, the List property points to the same reference.
WriteLine($"ReferenceEquals (venom & eddieBrock): {ReferenceEquals(venom, eddieBrock)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (venom & carnage): {ReferenceEquals(venom, carnage)}"); // False. Operator with creates a new reference variable.
WriteLine("");
eddieBrock.MutableList = new List<string>();
eddieBrock.MutableProperty = 3;
WriteLine($"Equals (venom & eddieBrock): {Equals(venom, eddieBrock)}"); // True. Reference or value type does not matter. Still a shallow copy of venom, still true.
WriteLine($"Equals (venom & carnage): {Equals(venom, carnage)}"); // False. the venom.List property does not points to the same reference like in carnage.List anymore.
WriteLine($"ReferenceEquals (venom & eddieBrock): {ReferenceEquals(venom, eddieBrock)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (venom & carnage): {ReferenceEquals(venom, carnage)}"); // False. Operator with creates a new reference variable.
WriteLine($"ReferenceEquals (venom.List & carnage.List): {ReferenceEquals(venom.List, carnage.List)}"); // True. Non mutable reference type.
WriteLine($"ReferenceEquals (venom.MutableList & carnage.MutableList): {ReferenceEquals(venom.MutableList, carnage.MutableList)}"); // False. This is why Equals(venom, carnage) returns false.
WriteLine("");
public record SomeRecord(List<string> List);
public record RecordOnlyWithValueNonMutableProperty(int NonMutableProperty);
public record RecordOnlyWithValueMutableProperty
{
public int MutableProperty { get; set; } = 1; // this property gets boxed
}
public record MixedRecord(List<string> List, int NonMutableProperty)
{
public List<string> MutableList { get; set; } = new();
public int MutableProperty { get; set; } = 1; // this property gets boxed
}
La penalización de rendimiento es obvia aquí. A mayor cantidad de datos para copiar en una instancia de registro que tenga, mayor penalización de rendimiento obtendrá. En general, debe crear clases pequeñas y livianas y esta regla también se aplica a los registros.
Si su aplicación usa una base de datos o un sistema de archivos, no me preocuparía mucho por esta penalización. Las operaciones de la base de datos/sistema de archivos son generalmente más lentas.
El problema fundamental es que antes de ahora, las interfaces abstractas (es decir, la palabra clave de la interfaz) no admitían funciones estáticas. Esto no era solo una limitación de C#, el CLR en sí no lo admitía antes de la introducción de la característica de métodos abstractos estáticos en las interfaces. Al igual que con los métodos de interfaz, los métodos abstractos estáticos pueden tener una implementación predeterminada, pero existen limitaciones.
Veamos interfaz IParsable<TSelf> ofrece dos métodos, Parse y TryParse. Ambos aceptan un String y un IFormatProvider. Para acceder a ellos, necesita un método de ayuda genérico como:
static T Parse<T>(string s, IFormatProvider? provider)
where T : IParsable<T>
{
return T.Parse(s, provider);
}
Se puede invocarlo usando un parámetro de tipo.
var a = Parse<int>("4", null);
La idea es que el compilador exponga información del proceso de compilación para crear herramientas que permitan mejorar ese código.
Los compiladores procesan el código siguiendo reglas estructuradas que a menudo difieren de la forma en que los humanos leen y entienden el código. Una comprensión básica del modelo utilizado por los compiladores es esencial para comprender las API que utiliza al crear herramientas basadas en Roslyn.
El SDK de .NET Compiler Platform expone el análisis de código de los compiladores de C# y Visual Basic, al proporcionar una capa de API que refleja una canalización de compilador tradicional.
Cada fase de este proceso es un componente separado. Primero, la fase de análisis tokeniza y analiza el texto fuente en sintaxis que sigue la gramática del lenguaje. En segundo lugar, la fase de declaración analiza la fuente y los metadatos importados para formar símbolos con nombre. A continuación, la fase de enlace hace coincidir los identificadores en el código con los símbolos. Finalmente, la fase de emisión emite un ensamblado con toda la información construida por el compilador.
En correspondencia con cada una de esas fases, el SDK de .NET Compiler Platform expone un modelo de objeto que permite el acceso a la información en esa fase. La fase de análisis expone un árbol de sintaxis, la fase de declaración expone una tabla de símbolos jerárquicos, la fase de vinculación expone el resultado del análisis semántico del compilador y la fase de emisión es una API que produce códigos de bytes IL.
Y porque es tan importante esto? .NET 7 es una plataforma de desarrollo multiplataforma unificada.
La idea era unificar .NET Core (reescrito desde cero, open source y multiplataforma) con la tecnología de .NET Framework (más versátil, pero limitada a sistemas Windows), así como con productos como Xamarin y Mono, para crear un producto que permitiera programar sobre una única base de código con runtimes y experiencias de desarrollo uniformes.
Sin embargo, en agosto de 2020, Microsoft anunció que estos pasos se pospondrían hasta el lanzamiento de .NET 6, previsto para noviembre de 2021. Tras eso, la compañía anunció "cambios en el cronograma" que obligaron a postergar de nuevo la integración de componentes como MAUI (Multi-platform App UI). Y así hasta hoy.
Una de las grandes novedades de .NET 7 es la inclusión de la versión 11 de la sintaxis de C#, el lenguaje de programación estrella de Microsoft (F#, también ha sido actualizado).
Además, el anuncio de Microsoft incluye referencias a mejoras de rendimiento, especialmente en ARM64 (con un rendimiento hasta un 45% superior) y acuerdos de colaboración para desarrollar con .NET en Ubuntu Linux, y en los servidores Power System de IBM.
Dejo link : https://news.microsoft.com/es-xl/net-7-esta-disponible-hoy/
Por el contrario, una cadena nula no se refiere a una instancia de un objeto System.String y cualquier intento de llamar a un método en una cadena nula provoca una NullReferenceException. Sin embargo, puede utilizar cadenas nulas en operaciones de concatenación y comparación con otras cadenas. Los siguientes ejemplos ilustran algunos casos en los que una referencia a una cadena nula hace y no provoca que se lance una excepción:
Las operaciones de cadenas en .NET están altamente optimizadas y, en la mayoría de los casos, no afectan significativamente el rendimiento. Sin embargo, en algunos escenarios, como bucles que se ejecutan cientos o miles de veces, las operaciones de cadenas pueden afectar el rendimiento. La clase StringBuilder crea un búfer de cadenas que ofrece un mejor rendimiento para estos casos. La cadena StringBuilder también le permite reasignar caracteres individuales, algo que el tipo de datos de cadena incorporado no admite.
Vamos a hacer una web Api con F# y Entity Framework.
Primero hacer la web Api e instalar Entity Framework.
dotnet new webapi --language "F#"
dotnet add package Swashbuckle.AspNetCore --version 5.6.3
dotnet new tool-manifest
dotnet tool install dotnet-ef
dotnet tool install paket
dotnet paket convert-from-nuget
dotnet paket add Microsoft.EntityFrameworkCore.Sqlite
dotnet paket add EntityFrameworkCore.FSharp
code .
Ahora creamos el modelo:
Hice un archivo Prueba.fs :
Y hacemos el dbcontext :
Ahora debemos indicar que compile estas clases agregando estas entradas en el fsproj :
Luego compilamos
dotnet build
Si funciona debemos hacer la migración :
dotnet ef migrations add Initial
Y ahora agregamos la migración a fsproj :
Y ahora creamos la base :
dotnet ef database update
Y ahora agregamos el context a el Startup.fs :
Y luego hacemos el controler :
Y listo!
dotnet run
Yo agregue swagger para mayor detalles dejo link de github:
https://github.com/emanuelpeg/FsharpWebApiExample
Generics introduce el concepto de parámetros de tipo en .NET, lo que hace posible diseñar clases y métodos que difieren la especificación de uno o más tipos hasta que la clase o método sea declarado e instanciado por el código del cliente. Veamos un ejemplo:
Las clases y métodos genéricos combinan la reutilización, la seguridad de tipos y la eficiencia de una manera que sus contrapartes no genéricas no pueden. Los genéricos se utilizan con mayor frecuencia con las colecciones y los métodos que operan en ellas. El espacio de nombres System.Collections.Generic contiene varias clases de colección basadas en genéricos. Las colecciones no genéricas, como ArrayList, no se recomiendan y se mantienen por motivos de compatibilidad.
Veamos un ejemplo:
Un método declarado con los parámetros de tipo para su tipo o parámetros de retorno se denomina método genérico.
Los métodos AddorUpdate () y GetData () son métodos genéricos. El tipo de datos real del parámetro del elemento se especificará en el momento de instanciar la clase DataStore <T>, como se muestra a continuación.
Una clase no genérica puede incluir métodos genéricos especificando un parámetro de tipo entre paréntesis angulares con el nombre del método, como se muestra a continuación.
La estructura DateTime incluye los siguientes métodos para convertir una fecha y hora en una cadena.
Método | Descripción |
ToString | Convierte un valor DateTime en una cadena en el formato especificado de la referencia cultural actual. Permite pasar el formato de fecha por parámetros. |
ToShortDateString | formato M/d/yyyy |
ToShortTimeString | formato h:mm:ss |
ToLongDateString | formato dddd, MMMM d, yyyy |
ToLongTimeString | formato h:mm:ss tt |
Veamos un ejemplo: