(+34) 673 566 782 - (+34) 960 653 052 formacion@imaginagroup.com

C# 7: Conoce todas las novedades que han aportado sus diferentes versiones

La intención de esta guía es realizar un detallado repaso por todas las novedades que han ido aportando cada una de las diferentes versiones que forman parte de C# 7, estas son:

  • C# 7.0
  • C# 7.1
  • C# 7.2
  • C# 7.3

Nos iremos parando en cada una de ellas, analizando con detalle y con ejemplos todas estas novedades de este lenguaje de programación orientado a objetos desarrollado por Microsoft. ¿Te apuntas?

C# 7.0 Novedades

Variables out

En esta versión se ha mejorado la sintaxis existente que admite parámetros out. Ahora es posible declarar variables out en la lista de argumentos de una llamada a método, en lugar de escribir una instrucción de declaración distinta:

if (int.TryParse(input, out int result))
    Console.WriteLine(result);
else
    Console.WriteLine("Could not parse input");

Para mayor claridad, es posible especificar el tipo de la variable out, tal y como se muestra anteriormente. Pero el lenguaje admite el uso de una variable local con tipo implícito:

if (int.TryParse(input, out var answer))
    Console.WriteLine(answer);
else
    Console.WriteLine("Could not parse input");

Tuplas

Las tuplas estaban disponibles antes de C# 7.0, pero no eran eficientes ni compatibles con ningún lenguaje. Esto significaba que solo se podía hacer referencia a los elementos tupla como Item1, Item2, por ejemplo. Ahora existe compatibilidad de lenguaje con las tuplas, que permite usar nombres semánticos en los campos de una tupla mediante tipos de tupla nuevos y más eficientes.

Es posible crear una tupla asignando un valor a cada miembro, y, opcionalmente, proporcionando nombres semánticos a cada uno de los miembros de la tupla:

(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");

La tupla namedLetters contiene campos denominados Alpha y Beta. Esos nombres solo existen en tiempo de compilación y no se conservan, por ejemplo, al inspeccionar la tupla mediante la reflexión en tiempo de ejecución.

En la asignación de una tupla, también pueden especificarse los nombres de los campos a la derecha de la asignación:

var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");

Descartes

Un descarte es una variable de solo escritura con el nombre “_”. Es posible asignar todos los valores que queramos descartar a una única variable. Un descarte se parece a una variable no asignada, aunque no puede usarse en el código (excepto la instrucción de asignación).

Los descartes se admiten en los escenarios siguientes:

  • Al deconstruir tuplas o tipos definidos por el usuario.
  • Al realizar llamadas a métodos mediante parámetros out.
  • En una operación de coincidencia de patrones con las instrucciones is y switch.
  • Como un identificador independiente cuando quiera identificar explícitamente el valor de una asignación como descarte.

Por ejemplo, la siguiente llamada al método devuelve una tupla de tres donde el primer y el segundo valor se descartan y area es una variable declarada previamente para establecerse en el tercer componente correspondiente devuelto por GetCityInformation:

(_, _, area) = city.GetCityInformation(cityName);

Detección de patrones

La coincidencia de patrones es una característica que permite implementar la distribución de métodos en propiedades distintas al tipo de un objeto.

Las clases base y derivadas proporcionan distintas implementaciones. Las expresiones de coincidencia de patrones extienden este concepto para que se puedan implementar fácilmente patrones de distribución similares para tipos y elementos de datos que no se relacionan mediante una jerarquía de herencia.

La coincidencia de patrones admite expresiones is y switch. Cada una de ellas habilita la inspección de un objeto y sus propiedades para determinar si el objeto cumple el patrón buscado. La palabra clave when se usa para especificar reglas adicionales para el patrón.

if (input is int count)
    sum += count;

La instrucción switch actualizada tiene varias construcciones nuevas:

  • Se puede probar el tipo de la expresión switch en todas las etiquetas case. Como sucede con la expresión is, puede asignar una variable nueva a ese tipo.
  • Ahora el orden de las etiquetas case es importante. Se ejecuta la primera rama de la que se quiere obtener la coincidencia, mientras que el resto se omite.

Devoluciones y variables locales ref

Esta característica habilita algoritmos que usan y devuelven referencias a variables definidas en otro lugar. Por ejemplo, trabajar con matrices de gran tamaño y buscar una sola ubicación con determinadas características.

El método siguiente devuelve una referencia a ese almacenamiento en la matriz:

public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return ref matrix[i, j];
    throw new InvalidOperationException("Not found");
}

Se puede declarar el valor devuelto como un elemento ref y modificar ese valor en la matriz, como se muestra en el código siguiente:

public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return ref matrix[i, j];
    throw new InvalidOperationException("Not found");
}

El lenguaje C# tiene varias reglas que impiden el uso incorrecto de las variables locales y devoluciones de ref:

  • Es necesario agregar la palabra clave ref a la firma del método y a todas las instrucciones return de un método.

Esto evidencia que el método se devuelve por referencia a lo largo del método.

  • Se puede asignar ref return a una variable de valor, o bien a una variable ref.

El autor de la llamada controla si se copia el valor devuelto o no. La omisión del modificador ref al asignar el valor devuelto indica que el autor de la llamada quiere una copia del valor, no una referencia al almacenamiento.

  • No se puede asignar un valor devuelto de método estándar a una variable local ref.

No permite instrucciones como ref int i = sequence.Count();

  • No se puede devolver un elemento ref a una variable cuya duración se extienda más allá de la ejecución del método.

Esto significa que no se puede devolver una referencia a una variable local o a una variable con un ámbito similar.

  • Las ref locales y las devoluciones no se pueden usar con métodos asíncronos.

El compilador no puede identificar si una variable a la que se hace referencia se ha establecido en su valor final en la devolución del método asíncrono.

Funciones locales

Las funciones locales permiten declarar métodos en el contexto de otro método y facilitan que los lectores de la clase vean que el método local solo se llama desde el contexto en el que se declara.

Hay dos casos de uso comunes para las funciones locales:

  • Métodos de iterador públicos
  • Métodos asincrónicos públicos.

Ambos tipos de métodos generan código que informa de errores más tarde de lo que los programadores podrían esperar. En los métodos de iterador, las excepciones solo se observan al llamar a código que enumera la secuencia devuelta. En los métodos asincrónicos, las excepciones solo se observan cuando se espera al elemento Task devuelto. En el ejemplo siguiente se muestra la separación de la validación de parámetros de la implementación de iteradores mediante una función local:

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

La misma técnica se puede emplear con métodos async para asegurarse de que las excepciones derivadas de la validación de argumentos se inician antes de comenzar el trabajo asincrónico:

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

Más miembros con forma de expresión

En C# 6 se presentaron los miembros con forma de expresión para funciones de miembros y propiedades de solo lectura. C# 7.0 amplía los miembros permitidos que pueden implementarse como expresiones, y además se pueden implementar constructores, finalizadores y descriptores de acceso get y set en propiedades e indizadores.

En el código siguiente se muestran ejemplos de cada uno:

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");

private string label;

// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}

Expresiones throw

En C#, throw siempre ha sido una instrucción en vez de una expresión. Por lo tanto había construcciones de C# en las que no se podía usar. Esto incluye expresiones condicionales, expresiones de fusión nulas y algunas expresiones lambda.

La incorporación de miembros con forma de expresión agrega más ubicaciones donde las expresiones throw resultarían útiles. Para que se pueda escribir cualquiera de estas construcciones, C# 7.0 presenta las expresiones throw.

Esta adición facilita la escritura de código más basado en expresiones. No se necesitan instrucciones adicionales para la comprobación de errores.

Tipos de valor devueltos de async generalizados

La devolución de un objeto Task desde métodos asincrónicos puede presentar cuellos de botella de rendimiento en determinadas rutas de acceso. Task es un tipo de referencia, por lo que su uso implica la asignación de un objeto. En los casos en los que un método declarado con el modificador async devuelva un resultado en caché o se complete sincrónicamente, las asignaciones adicionales pueden resultar costosas si se producen en bucles ajustados.

La nueva característica de lenguaje implica que los tipos de valor devuelto de métodos asincrónicos no están limitados a Task, Task<T> y void. El tipo devuelto debe seguir cumpliendo con el patrón asincrónico, lo que significa que debe haber un método GetAwaiter accesible. Como ejemplo concreto, se ha agregado el tipo ValueTask a .NET Framework para sacar partido de esta nueva característica del lenguaje:

public async ValueTask<int> Func()
{
    await Task.Delay(100);
    return 5;
}

Mejoras en la sintaxis de literales numéricos

La lectura incorrecta de constantes numéricas puede complicar la comprensión del código cuando se lee por primera vez. En C# 7.0 se incluyen dos nuevas características para escribir números de la manera más legible para el uso previsto: literales binarios y separadores de dígitos.

En aquellos casos en que cree máscaras de bits, o siempre que una representación binaria de un número aumente la legibilidad del código, escriba el número en formato binario:

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

El 0b al principio de la constante indica que el número está escrito como número binario. Los números binarios pueden ser muy largos, por lo que a menudo resulta más fácil ver los patrones de bits si se introduce “_” como separador de dígitos, como se ha mostrado antes en la constante binaria.

El separador de dígitos puede aparecer en cualquier parte de la constante. En números de base 10, es habitual usarlo como separador de miles:

public const long BillionsAndBillions = 100_000_000_000;

El separador de dígitos también se puede usar con tipos decimal, float y double:

public const double AvogadroConstant = 6.022_140_857_747_474e23;

public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

C# 7.1 Novedades

Es la primera versión secundaria del lenguaje C#. Incluye la posibilidad de configurar el compilador para que coincida con una versión especificada del lenguaje, lo que permite aislar la decisión de actualizar las herramientas de la decisión de actualizar las versiones de lenguaje. Además incorpora el elemento de configuración de selección de versión de lenguaje, tres nuevas características de lenguaje y un nuevo comportamiento del compilador.

Async main

Un método async main permite usar await en el método Main. Anteriormente, hubiera sido necesario escribir lo siguiente:

static int Main()
{
    return DoAsyncWork().GetAwaiter().GetResult();
}

Ahora se puede escribir esto:

static async Task<int> Main()
{
    // This could also be replaced with the body
    // DoAsyncWork, including its await expressions:
    return await DoAsyncWork();
}

Si el programa no devuelve un código de salida, puede declarar un método Main que devuelva una Task:

Expresiones literales predeterminadas

Las expresiones literales predeterminadas constituyen una mejora con respecto a las expresiones de valor predeterminadas. Estas expresiones inicializan una variable en el valor predeterminado.

Donde anteriormente habría que escribir:

Func<string, bool> whereClause = default(Func<string, bool>);

Ahora, se puede pasar por alto el tipo del lado derecho de la inicialización:

Func<string, bool> whereClause = default;

Nombres de elementos de tupla inferidos

Muchas veces, cuando se inicializa una tupla, las variables usadas en el lado derecho de la asignación son las mismas que los nombres que querríamos dar a los elementos de tupla:

int count = 5;
string label = "Colors used in the map";
var pair = (count: count, label: label);

Ahora, los nombres de los elementos de tupla se pueden deducir de las variables empleadas para inicializar la tupla.

int count = 5;
string label = "Colors used in the map";
var pair = (count, label); // element names are "count" and "label"

Coincidencia de patrones en parámetros de tipo genérico

La expresión de patrón para is y el patrón de tipo switch pueden tener el tipo de un parámetro de tipo genérico. Esto puede ser especialmente útil al comprobar los tipos que pueden ser tipos struct o class y si quiere evitar la conversión boxing.

Generación de ensamblados de referencia

Existen dos nuevas opciones del compilador con las que se generan ensamblados solo de referencia:

  • refout
  • refonly

La opción -refout especifica una ruta de archivo donde el ensamblado de referencia debe mostrarse. Esto se traduce en metadataPeStream en la API de emisión.

-refout:filepath

filepath Es la ruta de archivo del ensamblado de referencia. Generalmente debe coincidir con el ensamblado principal.

La convención recomendada es colocar el ensamblado de referencia en una subcarpeta “ref/” con relación al ensamblado principal.

La opción -refonly indica que un ensamblado de referencia debe mostrarse en lugar de un ensamblado de implementación, como el resultado principal. El parámetro -refonly deshabilita de forma automática la generación de archivos PDB, ya que los ensamblados de referencia no pueden ejecutarse.

-refonly

Los ensamblados de solo metadatos tienen sus cuerpos de métodos reemplazados por un cuerpo throw null único, pero incluyen todos los miembros excepto los tipos anónimos. El motivo de usar cuerpos throw null es que PEVerify pueda ejecutar y pasar (por lo tanto, validar la integridad de los metadatos).

C# 7.2 Novedades

Mejoras de código eficiente seguro

Las características de lenguaje permiten trabajar con tipos de valor usando la semántica de referencia. Están diseñadas para aumentar el rendimiento minimizando la copia de tipos de valor sin usar las asignaciones de memoria asociadas al uso de tipos de referencia.

Las características incluyen:

  • El modificador in en los parámetros para especificar que un argumento se pasa mediante una referencia sin que el método al que se realiza una llamada lo modifique.
  • El modificador ref readonly en las devoluciones de método para indicar que un método devuelve su valor mediante una referencia, pero que no permite operaciones de escritura en el objeto.
  • La declaración readonly struct para indicar que una estructura es fija y que debería pasarse como parámetro in a los métodos de su miembro.
  • La declaración ref struct para indicar que un tipo de estructura tiene acceso directo a la memoria administrada y que siempre debe estar asignada a la pila.

Argumentos con nombre no finales

Las llamadas de método ya pueden usar argumentos con nombre que precedan a argumentos posicionales si están en la posición adecuada.

Con los argumentos con nombre ya no es necesario recordar o buscar el orden de los parámetros de la lista de parámetros de los métodos llamados. El parámetro de cada argumento se puede especificar por nombre de parámetro.

Por ejemplo, se puede llamar de la manera habitual a una función que imprime los detalles de un pedido mediante el envío de argumentos por posición, en el orden definido por la función.

PrintOrderDetails("Gift Shop", 31, "Red Mug");

Si no se conoce el orden de los parámetros pero sí sus nombres, se pueden enviar los argumentos en cualquier orden.

PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");

PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);

Los argumentos con nombre también mejoran la legibilidad del código al identificar lo que cada argumento representa.

En el método de ejemplo siguiente, sellerName no puede ser nulo ni un espacio en blanco. Como sellerName y productName son tipos de cadena, en lugar de enviar argumentos por posición, tiene sentido usar argumentos con nombre para eliminar la ambigüedad entre ambos y evitar confusiones para aquellos que lean el código.

Los argumentos con nombre, cuando se usan con argumentos posicionales, son válidos siempre que:

  • No vayan seguidos de ningún argumento posicional:
PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");

PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug");

 

  • Se usen en la posición correcta. En este ejemplo, el parámetro orderNum está en la posición correcta pero no se le asigna un nombre de manera explícita.

Sin embargo, los argumentos con nombre que no están en el orden correcto no son válidos si van seguidos de argumentos posicionales.

PrintOrderDetails(productName: "Red Mug", 31, "Gift Shop");

Caracteres de subrayado iniciales en literales numéricos

La implementación de la compatibilidad con separadores de dígitos en C# 7.0 no permitía que “_” fuera el primer carácter del valor literal. Los literales numéricos hexadecimales y binarios ya pueden empezar con un “_”.

Por ejemplo:

int binaryValue = 0b_0101_0101;

Modificador de acceso private protected

Presentamos un nuevo modificador de acceso compuesto: private protected. Indica que se puede tener acceso a un miembro mediante una clase o clases derivadas declaradas en un mismo ensamblado.

Mientras que protected internal permite el acceso a las clases derivadas o clases que se encuentran en un mismo ensamblado, private protected limita el acceso a los tipos derivados que se declaran en un mismo ensamblado.

Expresiones ref condicionales

La expresión condicional puede producir un resultado de referencia en lugar de un resultado de valor. Por ejemplo, podría escribir lo siguiente para recuperar una referencia al primer elemento en una de dos matrices:

La variable r es una referencia al primer valor de arr o otherArr.

ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]);

 C# 7.3 Novedades

Características:

  • Acceso a campos fijos sin anclar.
  • Posibilidad de volver a asignar variables locales ref.
  • Uso de inicializadores en matrices stackalloc.
  • Uso de instrucciones fixed con cualquier tipo que admita un patrón.
  • Uso de restricciones genéricas adicionales.

Habilitación de código seguro más eficaz

La escritura de código seguro y no seguro, tiene el mismo rendimiento. El código seguro evita clases de errores, como desbordamientos de búfer, punteros perdidos y otros errores de acceso a la memoria.

Estas nuevas características amplían las capacidades de código seguro comprobable y lo facilitan en gran medida, ya que se puede escribir más código utilizando construcciones seguras.

Indexación de campos fixed sin requerir anclaje

Teniendo en cuenta esta estructura:

unsafe struct S
{
    public fixed int myFixedField[10];
}

En versiones anteriores de C#, era necesario anclar una variable para acceder a uno de los enteros que forman parte de myFixedField. Ahora, el código siguiente se compila sin anclar la variable p dentro de una instrucción fixed independiente:

class C
{
    static S s = new S();

    unsafe public void M()
    {
        int p = s.myFixedField[5];
    }
}

La variable p tiene acceso a un elemento en myFixedField. No es necesario declarar otra variable int* independiente. En versiones anteriores de C#, es necesario declarar un segundo puntero fijo:

class C
{
    static S s = new S();

    unsafe public void M()
    {
        fixed (int* ptr = s.myFixedField)
        {
            int p = ptr[5];
        }
    }
}

Las variables locales de ref se pueden reasignar

Ahora, las variables locales de ref pueden reasignarse para hacer referencia a instancias diferentes después de haberse inicializado.

El código siguiente ahora compila:

ref VeryLargeStruct refLocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; 

Las matrices stackalloc admiten inicializadores

Cuando inicializamos una matriz podemos especificar los valores para los elementos:

var arr = new int[3] {1, 2, 3};
var arr2 = new int[] {1, 2, 3};

Ahora, esa misma sintaxis se puede aplicar a las matrices que se declaran con stackalloc:

int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};

Hay más tipos compatibles con la instrucción fixed

La instrucción fixed admite un conjunto limitado de tipos. A partir de C# 7.3, cualquier tipo que contenga un método GetPinnableReference() que devuelve un ref T, o ref readonly T puede ser fixed.

La adición de esta característica significa que fixed puede utilizarse con System.Span<T> y tipos relacionados.

Restricciones genéricas mejoradas

Ahora se puede especificar el tipo System.Enum o System.Delegate como restricciones de clase base para un parámetro de tipo.

También se puede usar la nueva restricción unmanaged para especificar que el parámetro de tipo debe ser un tipo no administrado. Un tipo no administrado es un tipo que no es un tipo de referencia y no contiene ningún tipo de referencia en ningún nivel de anidamiento.

Compatibilidad con tuplas == y !=

Los tipos de tupla admiten los operadores == y !=. Estos operadores funcionan comparando cada uno de los miembros del argumento izquierdo con los miembros del argumento derecho en orden. Estas comparaciones dejarán de evaluar a los miembros en cuanto un par no sea igual. Los siguientes ejemplos de código usan ==, pero todas las reglas de comparación se aplican a !=.

En el siguiente ejemplo de código se muestra una comparación de igualdad para dos pares de enteros:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
Console.WriteLine(left == right); // displays 'true'

Hay varias reglas que hacen que las pruebas de igualdad de tupla sean más prácticas. La igualdad de tupla realiza conversiones elevadas si una de las tuplas es una tupla que admite valores NULL, como se muestra en el siguiente código:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
(int a, int b)? nullableTuple = right;
Console.WriteLine(left == nullableTuple);

La igualdad de tupla también realiza conversiones implícitas en cada uno de los miembros de ambas tuplas. Entre estas se incluyen conversiones elevadas, conversiones de ampliación u otras conversiones implícitas.

// lifted conversions
var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Also true

// converted type of left is (long, long)
(long a, long b) longTuple = (5, 10);
Console.WriteLine(left == longTuple); // Also true

// comparisons performed on (long, long) tuples
(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Also true

Los nombres de los miembros de las tuplas no participan en las pruebas para la igualdad. Sin embargo, si uno de los operandos es un literal de tupla con nombres explícitos, el compilador genera la advertencia CS8383 si esos nombres no coinciden con los nombres del otro operando.

Cuando ambos operandos son literales de tupla, la advertencia está en el operando derecho como se muestra en el siguiente ejemplo:

(int a, string b) pair = (1, "Hello");
(int z, string y) another = (1, "Hello");
Console.WriteLine(pair == another);
Console.WriteLine(pair == (z: 1, y: "Hello"));

Por último, las tuplas pueden contener tuplas anidadas. La igualdad de tupla compara la “forma” de cada operando a través de tuplas anidadas como se muestra en el siguiente ejemplo:

(int, (int, int)) nestedTuple = (1, (2, 3));
Console.WriteLine(nestedTuple == (1, (2, 3)) );

Asociación de atributos a los campos de respaldo para las propiedades autoimplementadas

El atributo SomeThingAboutFieldAttribute se aplica al campo de respaldo generado por el compilador para SomeProperty.

Esta sintaxis ahora se admite:

[field: SomeThingAboutFieldAttribute]
public int SomeProperty { get; set; }

Factor de desempate de resolución de sobrecargas del método in

Cuando se agregó el modificador de argumentos in, estos dos métodos causaron una ambigüedad:

static void M(S arg);
static void M(in S arg);

Ahora, la sobrecarga por valor es mejor que la de la versión de referencia de solo lectura. Para llamar a la versión con el argumento de referencia de solo lectura, debe incluir el modificador in cuando llame al método.

Ampliación de variables de la expresión en inicializadores

La sintaxis que se agregó en C# 7.0 para permitir declaraciones de variable out se ha ampliado para incluir inicializadores de campo, inicializadores de propiedad, inicializadores de constructor y cláusulas de consulta.

Esto permite código como el siguiente ejemplo:

public class B
{
   public B(int i, out int j)
   {
      j = i;
   }
}

public class D : B
{
   public D(int i) : base(i, out var j)
   {
      Console.WriteLine($"The value of 'j' is {j}");
   }
}

Mejoras en los candidatos de sobrecarga

En cada versión, las reglas de resolución de sobrecarga se actualizan. Esta versión agrega tres nuevas reglas para ayudar a que el compilador elija la opción obvia:

  1. Cuando un grupo de métodos contiene tanto miembros de instancia como estáticos, el compilador descarta los miembros de la instancia si el método fue invocado sin un receptor o contexto de instancia. Cuando no hay ningún receptor, el compilador incluye solo miembros estáticos en un contexto estático; de lo contrario, miembros estáticos y de instancia. Cuando el receptor es ambiguamente una instancia o un tipo, el compilador incluye ambos.
  2. Cuando un grupo de métodos contiene algunos métodos genéricos cuyos argumentos de tipo no cumplen con sus restricciones, estos miembros se eliminan del conjunto de candidatos.
  3. En el caso de una conversión de grupo de métodos, los métodos candidatos cuyo tipo de valor devuelto no coincide con el tipo de valor devuelto del delegado se eliminan del conjunto.

Firma pública o de código abierto

La opción del compilador -publicsign indica al compilador que firme el ensamblado con una clave pública. El ensamblado se marca como firmado, pero la firma se toma de la clave pública. Esta opción le permite crear ensamblados firmados a partir de proyectos de código abierto utilizando una clave pública.

La opción del compilador -pathmap indica al compilador que reemplace las rutas de acceso de origen del entorno de compilación por rutas de acceso de origen asignadas. La opción -pathmap controla la ruta de acceso de origen escrita por el compilador en los archivos PDB o para CallerFilePathAttribute.

Si quieres aprender más recuerda que tiene estos cursos con nosotros…

Uso de cookies: Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies

ACEPTAR
Aviso de cookies