C# Language
Características de C # 6.0
Buscar..
Introducción
Esta sexta iteración del lenguaje C # es proporcionada por el compilador de Roslyn. Este compilador salió con la versión 4.6 de .NET Framework, sin embargo, puede generar código de una manera compatible con versiones anteriores para permitir el destino de versiones anteriores del marco. El código de la versión 6 de C # se puede compilar de una manera totalmente compatible con .NET 4.0. También se puede usar para marcos anteriores, sin embargo, algunas características que requieren un soporte de marco adicional pueden no funcionar correctamente.
Observaciones
La sexta versión de C # se lanzó en julio de 2015 junto con Visual Studio 2015 y .NET 4.6.
Además de agregar algunas características nuevas de lenguaje, incluye una reescritura completa del compilador. Anteriormente, csc.exe
era una aplicación Win32 nativa escrita en C ++, con C # 6 ahora es una aplicación administrada .NET escrita en C #. Esta reescritura se conoció como proyecto "Roslyn" y el código ahora es de código abierto y está disponible en GitHub .
Nombre del operador
El operador nameof
devuelve el nombre de un elemento de código como una string
. Esto es útil cuando se lanzan excepciones relacionadas con los argumentos de los métodos y también cuando se implementa INotifyPropertyChanged
.
public string SayHello(string greeted)
{
if (greeted == null)
throw new ArgumentNullException(nameof(greeted));
Console.WriteLine("Hello, " + greeted);
}
El operador nameof
se evalúa en el momento de la compilación y cambia la expresión a una cadena literal. Esto también es útil para las cadenas que llevan el nombre de su miembro que las expone. Considera lo siguiente:
public static class Strings
{
public const string Foo = nameof(Foo); // Rather than Foo = "Foo"
public const string Bar = nameof(Bar); // Rather than Bar = "Bar"
}
Dado que nameof
expresiones nameof
expresiones son constantes en tiempo de compilación, se pueden usar en atributos, etiquetas de case
, declaraciones de switch
, etc.
Es conveniente usar nameof
con Enum
s. En lugar de:
Console.WriteLine(Enum.One.ToString());
Es posible utilizar:
Console.WriteLine(nameof(Enum.One))
La salida será One
en ambos casos.
El operador nameof
puede acceder a miembros no estáticos utilizando una sintaxis similar a la estática. En lugar de hacer:
string foo = "Foo";
string lengthName = nameof(foo.Length);
Puede ser reemplazado por:
string lengthName = nameof(string.Length);
La salida será Length
en ambos ejemplos. Sin embargo, este último impide la creación de instancias innecesarias.
Aunque el operador nameof
funciona con la mayoría de las construcciones de lenguaje, existen algunas limitaciones. Por ejemplo, no puede usar el operador nameof
en tipos genéricos abiertos o valores de retorno de método:
public static int Main()
{
Console.WriteLine(nameof(List<>)); // Compile-time error
Console.WriteLine(nameof(Main())); // Compile-time error
}
Además, si lo aplica a un tipo genérico, el parámetro de tipo genérico se ignorará:
Console.WriteLine(nameof(List<int>)); // "List"
Console.WriteLine(nameof(List<bool>)); // "List"
Para más ejemplos, vea este tema dedicado a nameof
.
Solución para versiones anteriores ( más detalles )
Aunque el operador nameof
no existe en C # para las versiones anteriores a 6.0, se puede tener una funcionalidad similar utilizando MemberExpression
como se MemberExpression
a continuación:
Expresión:
public static string NameOf<T>(Expression<Func<T>> propExp)
{
var memberExpression = propExp.Body as MemberExpression;
return memberExpression != null ? memberExpression.Member.Name : null;
}
public static string NameOf<TObj, T>(Expression<Func<TObj, T>> propExp)
{
var memberExpression = propExp.Body as MemberExpression;
return memberExpression != null ? memberExpression.Member.Name : null;
}
Uso:
string variableName = NameOf(() => variable);
string propertyName = NameOf((Foo o) => o.Bar);
Tenga en cuenta que este enfoque hace que se cree un árbol de expresiones en cada llamada, por lo que el rendimiento es mucho peor en comparación con el nameof
operador, que se evalúa en el momento de la compilación y no tiene sobrecarga en el tiempo de ejecución.
Miembros de la función de cuerpo expresivo
Los miembros de la función con cuerpo de expresión permiten el uso de expresiones lambda como cuerpos miembros. Para miembros simples, puede resultar en un código más limpio y más legible.
Las funciones con cuerpo de expresión se pueden usar para propiedades, indizadores, métodos y operadores.
Propiedades
public decimal TotalPrice => BasePrice + Taxes;
Es equivalente a:
public decimal TotalPrice
{
get
{
return BasePrice + Taxes;
}
}
Cuando se utiliza una función de expresión con una propiedad, la propiedad se implementa como una propiedad solo para el captador.
Indexadores
public object this[string key] => dictionary[key];
Es equivalente a:
public object this[string key]
{
get
{
return dictionary[key];
}
}
Métodos
static int Multiply(int a, int b) => a * b;
Es equivalente a:
static int Multiply(int a, int b)
{
return a * b;
}
Que también se puede utilizar con métodos de void
:
public void Dispose() => resource?.Dispose();
Se podría agregar una anulación de ToString
a la clase Pair<T>
:
public override string ToString() => $"{First}, {Second}";
Además, este enfoque simplista funciona con la palabra clave de override
:
public class Foo
{
public int Bar { get; }
public string override ToString() => $"Bar: {Bar}";
}
Los operadores
Esto también puede ser utilizado por los operadores:
public class Land
{
public double Area { get; set; }
public static Land operator +(Land first, Land second) =>
new Land { Area = first.Area + second.Area };
}
Limitaciones
Los miembros de la función con cuerpo de expresión tienen algunas limitaciones. No pueden contener declaraciones de bloque y cualquier otra declaración que contenga bloques: if
, switch
, for
, foreach
, while
, do
, try
, etc.
Algunas if
declaraciones pueden ser reemplazadas por operadores ternarios. Algunas declaraciones for
y foreach
se pueden convertir en consultas LINQ, por ejemplo:
IEnumerable<string> Digits
{
get
{
for (int i = 0; i < 10; i++)
yield return i.ToString();
}
}
IEnumerable<string> Digits => Enumerable.Range(0, 10).Select(i => i.ToString());
En todos los demás casos, se puede usar la sintaxis antigua para miembros de funciones.
Los miembros de la función con cuerpo de expresión pueden contener async
/ await
, pero a menudo es redundante:
async Task<int> Foo() => await Bar();
Puede ser reemplazado por:
Task<int> Foo() => Bar();
Filtros de excepción
Los filtros de excepción les dan a los desarrolladores la capacidad de agregar una condición (en forma de expresión boolean
) a un bloque catch , permitiendo que la catch
ejecute solo si la condición se evalúa como true
.
Los filtros de excepción permiten la propagación de información de depuración en la excepción original, mientras que al usar una instrucción if
dentro de un bloque catch
y volver a lanzar la excepción, se detiene la propagación de información de depuración en la excepción original. Con los filtros de excepción, la excepción continúa propagándose hacia arriba en la pila de llamadas a menos que se cumpla la condición. Como resultado, los filtros de excepción hacen que la experiencia de depuración sea mucho más fácil. En lugar de detenerse en la declaración de throw
, el depurador se detendrá en la instrucción de lanzar la excepción, con el estado actual y todas las variables locales conservadas. Los vertederos se ven afectados de manera similar.
Los filtros de excepción han sido admitidos por el CLR desde el principio y han sido accesibles desde VB.NET y F # durante más de una década al exponer una parte del modelo de manejo de excepciones del CLR. Solo después del lanzamiento de C # 6.0, la funcionalidad también estuvo disponible para los desarrolladores de C #.
Usando filtros de excepción
Los filtros de excepción se utilizan agregando una cláusula when
a la expresión catch
. Es posible usar cualquier expresión que devuelva un bool
en una cláusula when
(excepto en espera ). La variable de excepción declarada ex
es accesible desde dentro de la cláusula when
:
var SqlErrorToIgnore = 123;
try
{
DoSQLOperations();
}
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore)
{
throw new Exception("An error occurred accessing the database", ex);
}
Se pueden combinar múltiples bloques catch
con las cláusulas when
. La primera when
cláusula devuelva true
provocará que se detecte la excepción. Se ingresará su bloque catch
, mientras que las otras cláusulas catch
se ignorarán (no se evaluarán sus cláusulas when
). Por ejemplo:
try
{ ... }
catch (Exception ex) when (someCondition) //If someCondition evaluates to true,
//the rest of the catches are ignored.
{ ... }
catch (NotImplementedException ex) when (someMethod()) //someMethod() will only run if
//someCondition evaluates to false
{ ... }
catch(Exception ex) // If both when clauses evaluate to false
{ ... }
Arriesgado cuando la cláusula
Precaución
Puede ser arriesgado usar filtros de excepción: cuando se lanza una
Exception
desde dentro de la cláusulawhen
, laException
de la cláusulawhen
se ignora y se trata comofalse
. Este enfoque permite a los desarrolladores escribirwhen
cláusula sin tener en cuenta los casos no válidos.
El siguiente ejemplo ilustra tal escenario:
public static void Main()
{
int a = 7;
int b = 0;
try
{
DoSomethingThatMightFail();
}
catch (Exception ex) when (a / b == 0)
{
// This block is never reached because a / b throws an ignored
// DivideByZeroException which is treated as false.
}
catch (Exception ex)
{
// This block is reached since the DivideByZeroException in the
// previous when clause is ignored.
}
}
public static void DoSomethingThatMightFail()
{
// This will always throw an ArgumentNullException.
Type.GetType(null);
}
Tenga en cuenta que los filtros de excepción evitan los confusos problemas de número de línea asociados con el uso de throw
cuando el código que falla está dentro de la misma función. Por ejemplo, en este caso, el número de línea se reporta como 6 en lugar de 3:
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) {
6. throw;
7. }
El número de línea de excepción se informa como 6 porque el error se detectó y se volvió a lanzar con la declaración de throw
en la línea 6.
Lo mismo no ocurre con los filtros de excepción:
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) when (a != 0) {
6. throw;
7. }
En este ejemplo a
es 0, la cláusula catch
se ignora, pero 3 se reporta como número de línea. Esto se debe a que no desenrollan la pila . Más específicamente, la excepción no se detecta en la línea 5, porque a
de hecho es igual a 0
y por lo tanto no hay oportunidad para la excepción que ser re-lanzado en la línea 6, porque la línea 6 no se ejecuta.
La tala como efecto secundario
Las llamadas de método en la condición pueden causar efectos secundarios, por lo que los filtros de excepción se pueden usar para ejecutar código en excepciones sin detectarlos. Un ejemplo común que aprovecha esto es un método de Log
que siempre devuelve false
. Esto permite rastrear la información de registro mientras se realiza la depuración sin la necesidad de volver a lanzar la excepción.
Tenga en cuenta que si bien esto parece ser una forma cómoda de registro, puede ser riesgoso, especialmente si se utilizan ensamblajes de registro de terceros. Estos pueden generar excepciones al iniciar sesión en situaciones no obvias que pueden no detectarse fácilmente (consulte Risky
when(...)
cláusula anterior).
try
{
DoSomethingThatMightFail(s);
}
catch (Exception ex) when (Log(ex, "An error occurred"))
{
// This catch block will never be reached
}
// ...
static bool Log(Exception ex, string message, params object[] args)
{
Debug.Print(message, args);
return false;
}
El enfoque común en versiones anteriores de C # era registrar y volver a lanzar la excepción.
try
{
DoSomethingThatMightFail(s);
}
catch (Exception ex)
{
Log(ex, "An error occurred");
throw;
}
// ...
static void Log(Exception ex, string message, params object[] args)
{
Debug.Print(message, args);
}
El bloque finally
El bloque finally
se ejecuta cada vez si se lanza la excepción o no. Una sutileza con expresiones en when
se ejecutan los filtros de excepción más arriba en la pila antes de ingresar a los bloques internos por finally
. Esto puede provocar que los resultados y comportamientos inesperados cuando el código intenta modificar el estado global (como usuario o la cultura del hilo actual) y poner de nuevo en un finally
de bloquear.
Ejemplo: finally
bloque
private static bool Flag = false;
static void Main(string[] args)
{
Console.WriteLine("Start");
try
{
SomeOperation();
}
catch (Exception) when (EvaluatesTo())
{
Console.WriteLine("Catch");
}
finally
{
Console.WriteLine("Outer Finally");
}
}
private static bool EvaluatesTo()
{
Console.WriteLine($"EvaluatesTo: {Flag}");
return true;
}
private static void SomeOperation()
{
try
{
Flag = true;
throw new Exception("Boom");
}
finally
{
Flag = false;
Console.WriteLine("Inner Finally");
}
}
Salida producida:
comienzo
EvaluatesTo: True
Interior finalmente
Captura
Exterior finalmente
En el ejemplo anterior, si el método SomeOperation
no desea a "fugas" de los cambios de estado globales a quien haya llamado when
cláusulas, sino que también deben contener un catch
bloque para modificar el estado. Por ejemplo:
private static void SomeOperation()
{
try
{
Flag = true;
throw new Exception("Boom");
}
catch
{
Flag = false;
throw;
}
finally
{
Flag = false;
Console.WriteLine("Inner Finally");
}
}
También es común ver clases de ayuda IDisposable
aprovechan la semántica del uso de bloques para lograr el mismo objetivo, ya que IDisposable.Dispose
siempre se llamará antes de que una excepción llamada dentro de un bloque using
comience a burbujear la pila.
Inicializadores de propiedad automática
Introducción
Las propiedades pueden inicializarse con el operador =
después del cierre }
. La clase de Coordinate
continuación muestra las opciones disponibles para inicializar una propiedad:
public class Coordinate
{
public int X { get; set; } = 34; // get or set auto-property with initializer
public int Y { get; } = 89; // read-only auto-property with initializer
}
Accesorios con visibilidad diferente
Puede inicializar propiedades automáticas que tienen visibilidad diferente en sus accesores. Aquí hay un ejemplo con un setter protegido:
public string Name { get; protected set; } = "Cheeze";
El accesorio también puede ser internal
, internal protected
o private
.
Propiedades de solo lectura
Además de la flexibilidad con la visibilidad, también puede inicializar propiedades automáticas de solo lectura. Aquí hay un ejemplo:
public List<string> Ingredients { get; } =
new List<string> { "dough", "sauce", "cheese" };
Este ejemplo también muestra cómo inicializar una propiedad con un tipo complejo. Además, las propiedades automáticas no pueden ser de solo escritura, por lo que también impide la inicialización de solo escritura.
Estilo antiguo (pre C # 6.0)
Antes de C # 6, esto requería un código mucho más detallado. Estábamos usando una variable adicional llamada propiedad de respaldo de la propiedad para dar un valor predeterminado o para inicializar la propiedad pública como se muestra a continuación,
public class Coordinate
{
private int _x = 34;
public int X { get { return _x; } set { _x = value; } }
private readonly int _y = 89;
public int Y { get { return _y; } }
private readonly int _z;
public int Z { get { return _z; } }
public Coordinate()
{
_z = 42;
}
}
Nota: Antes de C # 6.0, aún podría inicializar las propiedades de lectura y escritura implementadas automáticamente (propiedades con un captador y un definidor) desde el constructor, pero no pudo inicializar la propiedad en línea con su declaración
Uso
Los inicializadores deben evaluar las expresiones estáticas, al igual que los inicializadores de campo. Si necesita hacer referencia a miembros no estáticos, puede inicializar propiedades en constructores como antes o usar propiedades con expresión. Las expresiones no estáticas, como la de abajo (comentadas), generarán un error de compilación:
// public decimal X { get; set; } = InitMe(); // generates compiler error
decimal InitMe() { return 4m; }
Pero los métodos estáticos se pueden utilizar para inicializar propiedades automáticas:
public class Rectangle
{
public double Length { get; set; } = 1;
public double Width { get; set; } = 1;
public double Area { get; set; } = CalculateArea(1, 1);
public static double CalculateArea(double length, double width)
{
return length * width;
}
}
Este método también se puede aplicar a propiedades con diferentes niveles de accesores:
public short Type { get; private set; } = 15;
El inicializador automático de propiedades permite la asignación de propiedades directamente dentro de su declaración. Para propiedades de solo lectura, se ocupa de todos los requisitos necesarios para garantizar que la propiedad sea inmutable. Considere, por ejemplo, la clase FingerPrint
en el siguiente ejemplo:
public class FingerPrint
{
public DateTime TimeStamp { get; } = DateTime.UtcNow;
public string User { get; } =
System.Security.Principal.WindowsPrincipal.Current.Identity.Name;
public string Process { get; } =
System.Diagnostics.Process.GetCurrentProcess().ProcessName;
}
Notas de precaución
Tenga cuidado de no confundir las propiedades automáticas o los inicializadores de campo con métodos de expresión similar que usan =>
en lugar de =
, y los campos que no incluyen { get; }
.
Por ejemplo, cada una de las siguientes declaraciones son diferentes.
public class UserGroupDto
{
// Read-only auto-property with initializer:
public ICollection<UserDto> Users1 { get; } = new HashSet<UserDto>();
// Read-write field with initializer:
public ICollection<UserDto> Users2 = new HashSet<UserDto>();
// Read-only auto-property with expression body:
public ICollection<UserDto> Users3 => new HashSet<UserDto>();
}
Falta { get; }
en los resultados de la declaración de propiedad en un campo público. Tanto la propiedad automática de solo lectura Users1
como el campo de lectura-escritura Users2
se inicializan solo una vez, pero un campo público permite cambiar la instancia de colección desde fuera de la clase, lo que generalmente no es deseable. Cambiar una propiedad automática de solo lectura con cuerpo de expresión a propiedad de solo lectura con inicializador requiere no solo eliminar >
from =>
, sino agregar { get; }
.
El símbolo diferente ( =>
lugar de =
) en Users3
da Users3
resultado que cada acceso a la propiedad devuelva una nueva instancia del HashSet<UserDto>
que, aunque C # válido (desde el punto de vista del compilador) es poco probable que sea el comportamiento deseado cuando utilizado para un miembro de la colección.
El código anterior es equivalente a:
public class UserGroupDto
{
// This is a property returning the same instance
// which was created when the UserGroupDto was instantiated.
private ICollection<UserDto> _users1 = new HashSet<UserDto>();
public ICollection<UserDto> Users1 { get { return _users1; } }
// This is a field returning the same instance
// which was created when the UserGroupDto was instantiated.
public virtual ICollection<UserDto> Users2 = new HashSet<UserDto>();
// This is a property which returns a new HashSet<UserDto> as
// an ICollection<UserDto> on each call to it.
public ICollection<UserDto> Users3 { get { return new HashSet<UserDto>(); } }
}
Inicializadores de índice
Los inicializadores de índice permiten crear e inicializar objetos con índices al mismo tiempo.
Esto hace que inicializar diccionarios sea muy fácil:
var dict = new Dictionary<string, int>()
{
["foo"] = 34,
["bar"] = 42
};
Cualquier objeto que tenga un getter o setter indexado se puede usar con esta sintaxis:
class Program
{
public class MyClassWithIndexer
{
public int this[string index]
{
set
{
Console.WriteLine($"Index: {index}, value: {value}");
}
}
}
public static void Main()
{
var x = new MyClassWithIndexer()
{
["foo"] = 34,
["bar"] = 42
};
Console.ReadKey();
}
}
Salida:
Índice: foo, valor: 34
Índice: barra, valor: 42
Si la clase tiene varios indizadores, es posible asignarlos a todos en un solo grupo de declaraciones:
class Program
{
public class MyClassWithIndexer
{
public int this[string index]
{
set
{
Console.WriteLine($"Index: {index}, value: {value}");
}
}
public string this[int index]
{
set
{
Console.WriteLine($"Index: {index}, value: {value}");
}
}
}
public static void Main()
{
var x = new MyClassWithIndexer()
{
["foo"] = 34,
["bar"] = 42,
[10] = "Ten",
[42] = "Meaning of life"
};
}
}
Salida:
Índice: foo, valor: 34
Índice: barra, valor: 42
Índice: 10, valor: diez
Índice: 42, valor: Significado de la vida.
Se debe tener en cuenta que el descriptor de acceso al set
del indexador podría comportarse de manera diferente en comparación con un método Add
(utilizado en inicializadores de colección).
Por ejemplo:
var d = new Dictionary<string, int>
{
["foo"] = 34,
["foo"] = 42,
}; // does not throw, second value overwrites the first one
versus:
var d = new Dictionary<string, int>
{
{ "foo", 34 },
{ "foo", 42 },
}; // run-time ArgumentException: An item with the same key has already been added.
Interpolación de cuerdas
La interpolación de cadenas permite al desarrollador combinar variables
y texto para formar una cadena.
Ejemplo básico
Se crean dos variables int
: foo
y bar
.
int foo = 34;
int bar = 42;
string resultString = $"The foo is {foo}, and the bar is {bar}.";
Console.WriteLine(resultString);
Salida :
El foo es 34, y la barra es 42.
Todavía se pueden usar llaves dentro de las cuerdas, como esto:
var foo = 34;
var bar = 42;
// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");
Esto produce el siguiente resultado:
El foo es {foo}, y la barra es {bar}.
Usando la interpolación con literales de cadena textual.
Usar @
antes de la cadena hará que la cadena sea interpretada textualmente. Entonces, por ejemplo, los caracteres Unicode o los saltos de línea se mantendrán exactamente como se han escrito. Sin embargo, esto no afectará las expresiones en una cadena interpolada como se muestra en el siguiente ejemplo:
Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
Salida: En caso de que no estuviera claro:
\ u00B9
El foo
es 34,
y el bar
es 42
Expresiones
Con la interpolación de cadenas, las expresiones entre llaves {}
también se pueden evaluar. El resultado se insertará en la ubicación correspondiente dentro de la cadena. Por ejemplo, para calcular el máximo de foo
y bar
e insertarlo, use Math.Max
dentro de las llaves:
Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");
Salida:
Y el mayor es: 42.
Nota: Cualquier espacio en blanco inicial o final (incluidos el espacio, la pestaña y CRLF / nueva línea) entre la llave y la expresión se ignoran por completo y no se incluyen en la salida
Como otro ejemplo, las variables se pueden formatear como una moneda:
Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");
Salida:
Foo formateado como una moneda con 4 decimales: $ 34.0000
O se pueden formatear como fechas:
Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");
Salida:
Hoy es: lunes 20 de julio de 2015.
Las declaraciones con un operador condicional (ternario) también se pueden evaluar dentro de la interpolación. Sin embargo, estos deben estar envueltos entre paréntesis, ya que los dos puntos se utilizan para indicar el formato como se muestra arriba:
Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");
Salida:
Bar es más grande que foo!
Se pueden mezclar expresiones condicionales y especificadores de formato:
Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");
Salida:
Medio ambiente: proceso de 32 bits
Secuencias de escape
Los caracteres de barra diagonal inversa ( \
) y comillas ( "
) funcionan exactamente igual en las cadenas interpoladas que en las cadenas no interpoladas, tanto para literales de cadena textuales como no literales:
Console.WriteLine($"Foo is: {foo}. In a non-verbatim string, we need to escape \" and \\ with backslashes.");
Console.WriteLine($@"Foo is: {foo}. In a verbatim string, we need to escape "" with an extra quote, but we don't need to escape \");
Salida:
Foo es 34. En una cadena no verbal, necesitamos escapar "y \ con barras invertidas.
Foo tiene 34. En una cadena textual, necesitamos escapar "con una cita extra, pero no necesitamos escapar \
Para incluir un corchete {
o }
en una cadena interpolada, use dos corchetes {{
o }}
:
$"{{foo}} is: {foo}"
Salida:
{foo} es: 34
Tipo FormattableString
El tipo de expresión de interpolación de cadena $"..."
no siempre es una cadena simple. El compilador decide qué tipo asignar según el contexto:
string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";
Este es también el orden de preferencia de tipo cuando el compilador necesita elegir a qué método sobrecargado se llamará.
Un nuevo tipo , System.FormattableString
, representa una cadena de formato compuesto, junto con los argumentos a formatear. Use esto para escribir aplicaciones que manejen los argumentos de interpolación específicamente:
public void AddLogItem(FormattableString formattableString)
{
foreach (var arg in formattableString.GetArguments())
{
// do something to interpolation argument 'arg'
}
// use the standard interpolation and the current culture info
// to get an ordinary String:
var formatted = formattableString.ToString();
// ...
}
Llame al método anterior con:
AddLogItem($"The foo is {foo}, and the bar is {bar}.");
Por ejemplo, uno podría elegir no incurrir en el costo de rendimiento de formatear la cadena si el nivel de registro ya iba a filtrar el elemento de registro. Conversiones implícitas
Hay conversiones de tipo implícitas de una cadena interpolada:
var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
También puede producir una variable IFormattable
que le permita convertir la cadena con un contexto invariante: var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";
Métodos de cultivo actuales e invariantes
Si el análisis de código está activado, todas las cadenas interpoladas producirán una advertencia CA1305 (Especifique IFormatProvider
). Se puede utilizar un método estático para aplicar la cultura actual.
public static class Culture
{
public static string Current(FormattableString formattableString)
{
return formattableString?.ToString(CultureInfo.CurrentCulture);
}
public static string Invariant(FormattableString formattableString)
{
return formattableString?.ToString(CultureInfo.InvariantCulture);
}
}
Luego, para producir una cadena correcta para la cultura actual, solo use la expresión:
Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
Nota : Current
e Invariant
no se pueden crear como métodos de extensión porque, de forma predeterminada, el compilador asigna el tipo String
a la expresión de cadena interpolada, lo que hace que el siguiente código no se compile: $"interpolated {typeof(string).Name} string.".Current();
FormattableString
clase FormattableString
ya contiene el método Invariant()
, por lo que la forma más sencilla de cambiar a una cultura invariante es using static
:
using static System.FormattableString;
string invariant = Invariant($"Now = {DateTime.Now}"); string current = $"Now = {DateTime.Now}";
Entre bastidores
Las cadenas interpoladas son solo un azúcar sintáctico para String.Format()
. El compilador ( Roslyn ) lo convertirá en un String.Format
detrás de escena:
var text = $"Hello {name + lastName}";
Lo anterior se convertirá en algo como esto:
string text = string.Format("Hello {0}", new object[] {
name + lastName
});
Interpolación de cuerdas y Linq
Es posible usar cadenas interpoladas en las declaraciones de Linq para aumentar aún más la legibilidad.
var fooBar = (from DataRow x in fooBarTable.Rows
select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();
Puede ser reescrito como:
var fooBar = (from DataRow x in fooBarTable.Rows
select $"{x["foo"]}{x["bar"]}").ToList();
Cuerdas interpoladas reutilizables
Con string.Format
, puede crear cadenas de formato reutilizables:
public const string ErrorFormat = "Exception caught:\r\n{0}";
// ...
Logger.Log(string.Format(ErrorFormat, ex));
Sin embargo, las cadenas interpoladas no se compilarán con los marcadores de posición que se refieren a variables no existentes. Lo siguiente no se compilará:
public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context
En su lugar, cree un Func<>
que consume variables y devuelve una String
:
public static Func<Exception, string> FormatError =
error => $"Exception caught:\r\n{error}";
// ...
Logger.Log(FormatError(ex));
Interpolación de cuerdas y localización.
Si está localizando su aplicación, puede preguntarse si es posible utilizar la interpolación de cadenas junto con la localización. De hecho, sería bueno tener la posibilidad de almacenar en archivos de recursos String
s como:
"My name is {name} {middlename} {surname}"
En lugar de mucho menos legible: "My name is {0} {1} {2}"
String
proceso de interpolación de String
se produce en tiempo de compilación , a diferencia del formato de cadena con cadena. string.Format
que ocurre en tiempo de ejecución . Las expresiones en una cadena interpolada deben hacer referencia a los nombres en el contexto actual y deben almacenarse en archivos de recursos. Eso significa que si quieres usar la localización tienes que hacerlo como:
var FirstName = "John";
// method using different resource file "strings"
// for French ("strings.fr.resx"), German ("strings.de.resx"),
// and English ("strings.en.resx")
void ShowMyNameLocalized(string name, string middlename = "", string surname = "")
{
// get localized string
var localizedMyNameIs = Properties.strings.Hello;
// insert spaces where necessary
name = (string.IsNullOrWhiteSpace(name) ? "" : name + " ");
middlename = (string.IsNullOrWhiteSpace(middlename) ? "" : middlename + " ");
surname = (string.IsNullOrWhiteSpace(surname) ? "" : surname + " ");
// display it
Console.WriteLine($"{localizedMyNameIs} {name}{middlename}{surname}".Trim());
}
// switch to French and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
ShowMyNameLocalized(FirstName);
// switch to German and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
ShowMyNameLocalized(FirstName);
// switch to US English and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
ShowMyNameLocalized(FirstName);
Si las cadenas de recursos para los idiomas utilizados anteriormente se almacenan correctamente en los archivos de recursos individuales, debe obtener el siguiente resultado:
Bonjour, mon nom est John
Hola, mein Nombre ist John
Hola, mi nombre es John
Tenga en cuenta que esto implica que el nombre sigue la cadena localizada en todos los idiomas. Si ese no es el caso, debe agregar marcadores de posición a las cadenas de recursos y modificar la función anterior o debe consultar la información de cultura en la función y proporcionar una declaración de cambio de caso que contenga los diferentes casos. Para obtener más detalles sobre los archivos de recursos, consulte Cómo usar la localización en C # .
Es una buena práctica usar un idioma alternativo predeterminado que la mayoría de la gente entenderá, en caso de que no haya una traducción disponible. Sugiero utilizar el inglés como idioma alternativo predeterminado.
Interpolación recursiva
Aunque no es muy útil, se permite usar una string
interpolada recursivamente dentro de los corchetes de otra persona:
Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:");
Console.WriteLine($"My class is called {nameof(MyClass)}.");
Salida:
La cadena tiene 27 caracteres:
Mi clase se llama MyClass.
Esperar en la captura y finalmente
Es posible usar la expresión de await
para aplicar el operador de espera a Tareas o Tarea (de resultados) en la catch
y, finally
bloques en C # 6.
No fue posible usar la expresión de await
en los bloqueos de catch
y finally
en versiones anteriores debido a las limitaciones del compilador. C # 6 hace que la espera de tareas asíncronas sea mucho más fácil al permitir la expresión de await
.
try
{
//since C#5
await service.InitializeAsync();
}
catch (Exception e)
{
//since C#6
await logger.LogAsync(e);
}
finally
{
//since C#6
await service.CloseAsync();
}
En C # 5 se requería usar un bool
o declarar una Exception
fuera del catch catch para realizar operaciones asíncronas. Este método se muestra en el siguiente ejemplo:
bool error = false;
Exception ex = null;
try
{
// Since C#5
await service.InitializeAsync();
}
catch (Exception e)
{
// Declare bool or place exception inside variable
error = true;
ex = e;
}
// If you don't use the exception
if (error)
{
// Handle async task
}
// If want to use information from the exception
if (ex != null)
{
await logger.LogAsync(e);
}
// Close the service, since this isn't possible in the finally
await service.CloseAsync();
Propagación nula
El ?.
operador y ?[...]
operador se llaman el operador condicional nulo . A veces también se hace referencia a otros nombres, como el operador de navegación segura .
Esto es útil, porque si el .
El operador (miembro de acceso) se aplica a una expresión que se evalúa como null
, el programa lanzará una NullReferenceException
. Si el desarrollador usa en su lugar el ?.
Operador (condicional nulo), la expresión se evaluará como nula en lugar de lanzar una excepción.
Tenga en cuenta que si el ?.
se usa el operador y la expresión es no nula, ?.
y .
son equivalentes
Lo esencial
var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null
Si el classroom
no tiene un profesor, GetTeacher()
puede devolver null
. Cuando es null
y se accede a la propiedad Name
, se lanzará una NullReferenceException
.
Si modificamos esta declaración para utilizar el ?.
Sintaxis, el resultado de toda la expresión será null
:
var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null
Posteriormente, si el classroom
también pudiera ser null
, también podríamos escribir esta declaración como:
var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null
Este es un ejemplo de cortocircuito: cuando cualquier operación de acceso condicional que utiliza el operador condicional nulo se evalúa como nulo, la expresión completa se evalúa como nula inmediatamente, sin procesar el resto de la cadena.
Cuando el miembro terminal de una expresión que contiene el operador condicional nulo es de un tipo de valor, la expresión se evalúa como un Nullable<T>
de ese tipo y, por lo tanto, no puede usarse como un reemplazo directo de la expresión sin ?.
.
bool hasCertification = classroom.GetTeacher().HasCertification;
// compiles without error but may throw a NullReferenceException at runtime
bool hasCertification = classroom?.GetTeacher()?.HasCertification;
// compile time error: implicit conversion from bool? to bool not allowed
bool? hasCertification = classroom?.GetTeacher()?.HasCertification;
// works just fine, hasCertification will be null if any part of the chain is null
bool hasCertification = classroom?.GetTeacher()?.HasCertification.GetValueOrDefault();
// must extract value from nullable to assign to a value type variable
Usar con el operador de unión nula (??)
Puede combinar el operador de condición nula con el operador de unión nula ( ??
) para devolver un valor predeterminado si la expresión se resuelve en null
. Usando nuestro ejemplo anterior:
var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher()
// returns null OR classroom is null OR Name is null
Usar con indexadores
El operador condicional nulo se puede utilizar con los indizadores :
var firstStudentName = classroom?.Students?[0]?.Name;
En el ejemplo anterior:
- ¿El primero
?.
Asegura que elclassroom
no seanull
. - El segundo
?
asegura que toda la colecciónStudents
no seanull
. - ¿El tercero
?.
después de que el indexador se asegure de que el indexador[0]
no devolvió un objetonull
. Se debe tener en cuenta que esta operación aún puede lanzar unaIndexOutOfRangeException
.
Usar con funciones vacias
El operador de condición nula también se puede utilizar con funciones de void
. Sin embargo, en este caso, la declaración no se evaluará como null
. Simplemente evitará una NullReferenceException
.
List<string> list = null;
list?.Add("hi"); // Does not evaluate to null
Utilizar con la invocación de eventos
Asumiendo la siguiente definición de evento:
private event EventArgs OnCompleted;
Cuando se invoca un evento, tradicionalmente, es una buena práctica verificar si el evento es null
en caso de que no haya suscriptores presentes:
var handler = OnCompleted;
if (handler != null)
{
handler(EventArgs.Empty);
}
Dado que se ha introducido el operador condicional nulo, la invocación se puede reducir a una sola línea:
OnCompleted?.Invoke(EventArgs.Empty);
Limitaciones
El operador condicional nulo produce rvalue, no lvalue, es decir, no se puede usar para la asignación de propiedades, suscripción de eventos, etc. Por ejemplo, el siguiente código no funcionará:
// Error: The left-hand side of an assignment must be a variable, property or indexer
Process.GetProcessById(1337)?.EnableRaisingEvents = true;
// Error: The event can only appear on the left hand side of += or -=
Process.GetProcessById(1337)?.Exited += OnProcessExited;
Gotchas
Tenga en cuenta que:
int? nameLength = person?.Name.Length; // safe if 'person' is null
no es lo mismo que
int? nameLength = (person?.Name).Length; // avoid this
porque lo primero corresponde a:
int? nameLength = person != null ? (int?)person.Name.Length : null;
y este último corresponde a:
int? nameLength = (person != null ? person.Name : null).Length;
A pesar del operador ternario ?:
Se utiliza aquí para explicar la diferencia entre dos casos, estos operadores no son equivalentes. Esto se puede demostrar fácilmente con el siguiente ejemplo:
void Main()
{
var foo = new Foo();
Console.WriteLine("Null propagation");
Console.WriteLine(foo.Bar?.Length);
Console.WriteLine("Ternary");
Console.WriteLine(foo.Bar != null ? foo.Bar.Length : (int?)null);
}
class Foo
{
public string Bar
{
get
{
Console.WriteLine("I was read");
return string.Empty;
}
}
}
Qué salidas:
Propagación nula
Fui leído
0
Ternario
Fui leído
Fui leído
0
Para evitar múltiples invocaciones equivalentes sería:
var interimResult = foo.Bar;
Console.WriteLine(interimResult != null ? interimResult.Length : (int?)null);
Y esta diferencia explica en parte por qué el operador de propagación nula aún no se admite en los árboles de expresiones.
Utilizando el tipo estático
El using static [Namespace.Type]
directiva using static [Namespace.Type]
permite la importación de miembros estáticos de tipos y valores de enumeración. Los métodos de extensión se importan como métodos de extensión (de un solo tipo), no al ámbito de nivel superior.
using static System.Console;
using static System.ConsoleColor;
using static System.Math;
class Program
{
static void Main()
{
BackgroundColor = DarkBlue;
WriteLine(Sqrt(2));
}
}
using System;
class Program
{
static void Main()
{
Console.BackgroundColor = ConsoleColor.DarkBlue;
Console.WriteLine(Math.Sqrt(2));
}
}
Resolución mejorada de sobrecarga
El siguiente fragmento de código muestra un ejemplo de cómo pasar un grupo de métodos (a diferencia de un lambda) cuando se espera un delegado. La resolución de sobrecarga ahora resolverá esto en lugar de generar un error de sobrecarga ambiguo debido a la capacidad de C # 6 para verificar el tipo de retorno del método que se pasó.
using System;
public class Program
{
public static void Main()
{
Overloaded(DoSomething);
}
static void Overloaded(Action action)
{
Console.WriteLine("overload with action called");
}
static void Overloaded(Func<int> function)
{
Console.WriteLine("overload with Func<int> called");
}
static int DoSomething()
{
Console.WriteLine(0);
return 0;
}
}
Resultados:
Error
error CS0121: la llamada es ambigua entre los siguientes métodos o propiedades: 'Program.Overloaded (System.Action)' y 'Program.Overloaded (System.Func)'
C # 6 también puede manejar bien el siguiente caso de coincidencia exacta para expresiones lambda que podría haber resultado en un error en C # 5 .
using System;
class Program
{
static void Foo(Func<Func<long>> func) {}
static void Foo(Func<Func<int>> func) {}
static void Main()
{
Foo(() => () => 7);
}
}
Cambios menores y correcciones de errores
Los paréntesis ahora están prohibidos alrededor de los parámetros nombrados. Lo siguiente se compila en C # 5, pero no en C # 6
Console.WriteLine((value: 23));
Los operandos de is
y as
ya no pueden ser grupos de métodos. Lo siguiente se compila en C # 5, pero no en C # 6
var result = "".Any is byte;
El compilador nativo lo permitió (aunque mostró una advertencia) y, de hecho, ni siquiera
1.Any is string
compatibilidad del método de extensión, permitiendo cosas locas como1.Any is string
oIDisposable.Dispose is object
.
Vea esta referencia para actualizaciones sobre cambios.
Usando un método de extensión para la inicialización de la colección
La sintaxis de inicialización de la colección se puede usar al crear instancias de cualquier clase que implemente IEnumerable
y tenga un método llamado Add
que tome un solo parámetro.
En versiones anteriores, este método Add
tenía que ser un método de instancia en la clase que se estaba inicializando. En C # 6, también puede ser un método de extensión.
public class CollectionWithAdd : IEnumerable
{
public void Add<T>(T item)
{
Console.WriteLine("Item added with instance add method: " + item);
}
public IEnumerator GetEnumerator()
{
// Some implementation here
}
}
public class CollectionWithoutAdd : IEnumerable
{
public IEnumerator GetEnumerator()
{
// Some implementation here
}
}
public static class Extensions
{
public static void Add<T>(this CollectionWithoutAdd collection, T item)
{
Console.WriteLine("Item added with extension add method: " + item);
}
}
public class Program
{
public static void Main()
{
var collection1 = new CollectionWithAdd{1,2,3}; // Valid in all C# versions
var collection2 = new CollectionWithoutAdd{4,5,6}; // Valid only since C# 6
}
}
Esto dará como resultado:
Artículo añadido con el método de agregar instancia: 1
Artículo añadido con el método de agregar instancia: 2
Elemento agregado con instancia agregar método: 3
Artículo agregado con extensión añadir método: 4
Artículo agregado con extensión añadir método: 5
Artículo agregado con extensión añadir método: 6
Deshabilitar las mejoras de advertencias
En C # 5.0 y anteriores, el desarrollador solo podía suprimir las advertencias por número. Con la introducción de los analizadores Roslyn, C # necesita una forma de deshabilitar las advertencias emitidas desde bibliotecas específicas. Con C # 6.0, la directiva pragma puede suprimir las advertencias por nombre.
Antes de:
#pragma warning disable 0501
C # 6.0:
#pragma warning disable CS0501