C# Language
C # 6.0 Caratteristiche
Ricerca…
introduzione
Questa sesta iterazione del linguaggio C # è fornita dal compilatore di Roslyn. Questo compilatore è uscito con la versione 4.6 di .NET Framework, tuttavia può generare codice in un modo compatibile con le versioni precedenti per consentire il targeting di versioni precedenti del framework. Il codice di versione 6 C # può essere compilato in modo completamente retroattivo con .NET 4.0. Può anche essere utilizzato per i framework precedenti, tuttavia alcune funzionalità che richiedono supporto framework aggiuntivo potrebbero non funzionare correttamente.
Osservazioni
La sesta versione di C # è stata rilasciata a luglio 2015 insieme a Visual Studio 2015 e .NET 4.6.
Oltre ad aggiungere alcune nuove funzionalità linguistiche include una completa riscrittura del compilatore. Precedentemente csc.exe
era un'applicazione nativa di Win32 scritta in C ++, con C # 6 ora è un'applicazione gestita .NET scritta in C #. Questa riscrittura era conosciuta come progetto "Roslyn" e il codice è ora open source e disponibile su GitHub .
Nome dell'operatore
L'operatore nameof
restituisce il nome di un elemento di codice come una string
. Ciò è utile quando si INotifyPropertyChanged
eccezioni relative agli argomenti del metodo e anche quando si implementa INotifyPropertyChanged
.
public string SayHello(string greeted)
{
if (greeted == null)
throw new ArgumentNullException(nameof(greeted));
Console.WriteLine("Hello, " + greeted);
}
L'operatore nameof
viene valutato al momento della compilazione e modifica l'espressione in una stringa letterale. Questo è utile anche per le stringhe che prendono il nome dal loro membro che le espone. Considera quanto segue:
public static class Strings
{
public const string Foo = nameof(Foo); // Rather than Foo = "Foo"
public const string Bar = nameof(Bar); // Rather than Bar = "Bar"
}
Dal nameof
espressioni nameof
sono costanti in fase di compilazione, possono essere utilizzate in attributi, etichette case
, istruzioni switch
e così via.
È conveniente usare nameof
con Enum
s. Invece di:
Console.WriteLine(Enum.One.ToString());
è possibile usare:
Console.WriteLine(nameof(Enum.One))
L'output sarà One
in entrambi i casi.
L'operatore nameof
può accedere ai membri non statici usando la sintassi di tipo statico. Invece di fare:
string foo = "Foo";
string lengthName = nameof(foo.Length);
Può essere sostituito con:
string lengthName = nameof(string.Length);
L'output sarà Length
in entrambi gli esempi. Tuttavia, quest'ultimo impedisce la creazione di istanze non necessarie.
Sebbene l'operatore nameof
con la maggior parte dei costrutti di linguaggio, esistono alcune limitazioni. Ad esempio, non è possibile utilizzare l'operatore nameof
tipi generici aperti o valori restituiti dal metodo:
public static int Main()
{
Console.WriteLine(nameof(List<>)); // Compile-time error
Console.WriteLine(nameof(Main())); // Compile-time error
}
Inoltre, se lo si applica a un tipo generico, il parametro di tipo generico verrà ignorato:
Console.WriteLine(nameof(List<int>)); // "List"
Console.WriteLine(nameof(List<bool>)); // "List"
Per ulteriori esempi, vedere questo argomento dedicato a nameof
.
Soluzione alternativa per le versioni precedenti ( maggiori dettagli )
Sebbene l'operatore nameof
non esista in C # per le versioni precedenti alla 6.0, è possibile utilizzare funzionalità simili utilizzando MemberExpression
come nell'esempio seguente:
Espressione:
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);
Si noti che questo approccio determina la creazione di un albero di espressioni per ogni chiamata, quindi le prestazioni sono molto peggiori rispetto all'operatore nameof
che viene valutato in fase di compilazione e che non ha sovraccarico in fase di esecuzione.
Membri della funzione con espressione corporea
I membri delle funzioni con corpo espressivo consentono l'uso di espressioni lambda come corpi membri. Per i membri semplici, può risultare in un codice più pulito e più leggibile.
Le funzioni con corpo espressivo possono essere utilizzate per proprietà, indicizzatori, metodi e operatori.
Proprietà
public decimal TotalPrice => BasePrice + Taxes;
È equivalente a:
public decimal TotalPrice
{
get
{
return BasePrice + Taxes;
}
}
Quando una funzione con un'espressione viene utilizzata con una proprietà, la proprietà viene implementata come proprietà di solo getter.
indicizzatori
public object this[string key] => dictionary[key];
È equivalente a:
public object this[string key]
{
get
{
return dictionary[key];
}
}
metodi
static int Multiply(int a, int b) => a * b;
È equivalente a:
static int Multiply(int a, int b)
{
return a * b;
}
Che può essere utilizzato anche con metodi void
:
public void Dispose() => resource?.Dispose();
Un override di ToString
potrebbe essere aggiunto alla classe Pair<T>
:
public override string ToString() => $"{First}, {Second}";
Inoltre, questo approccio semplicistico funziona con la parola chiave override
:
public class Foo
{
public int Bar { get; }
public string override ToString() => $"Bar: {Bar}";
}
operatori
Questo può anche essere utilizzato dagli operatori:
public class Land
{
public double Area { get; set; }
public static Land operator +(Land first, Land second) =>
new Land { Area = first.Area + second.Area };
}
limitazioni
I membri delle funzioni con corpo di espressione hanno alcune limitazioni. Non possono contenere istruzioni di blocco e qualsiasi altra istruzione che contenga blocchi: if
, switch
, for
, foreach
, while
, do
, try
, etc.
Alcuni if
dichiarazioni possono essere sostituite con operatori ternari. Alcune istruzioni for
e foreach
possono essere convertite in query LINQ, ad esempio:
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());
In tutti gli altri casi, è possibile utilizzare la vecchia sintassi per i membri delle funzioni.
I membri delle funzioni con corpo di espressione possono contenere async
/ await
, ma spesso sono ridondanti:
async Task<int> Foo() => await Bar();
Può essere sostituito con:
Task<int> Foo() => Bar();
Filtri di eccezione
I filtri di eccezione offrono agli sviluppatori la possibilità di aggiungere una condizione (sotto forma di espressione boolean
) a un blocco catch , consentendo l'esecuzione del catch
solo se la condizione è true
.
I filtri di eccezione consentono la propagazione delle informazioni di debug nell'eccezione originale, dove come utilizzando un'istruzione if
all'interno di un blocco catch
e il richiamo dell'eccezione interrompe la propagazione delle informazioni di debug nell'eccezione originale. Con i filtri delle eccezioni, l'eccezione continua a propagarsi verso l'alto nello stack di chiamate, a meno che la condizione non sia soddisfatta. Di conseguenza, i filtri delle eccezioni rendono l'esperienza di debug molto più semplice. Invece di fermarsi sull'istruzione throw
, il debugger si fermerà sull'istruzione che lancia l'eccezione, mantenendo lo stato corrente e tutte le variabili locali. Le discariche sono coinvolte in modo simile.
I filtri di eccezione sono stati supportati dal CLR sin dall'inizio e sono stati accessibili da VB.NET e F # per oltre un decennio esponendo una parte del modello di gestione delle eccezioni CLR. Solo dopo il rilascio di C # 6.0 la funzionalità è stata disponibile anche per gli sviluppatori C #.
Utilizzo dei filtri delle eccezioni
I filtri di eccezione vengono utilizzati aggiungendo una clausola when
all'espressione catch
. È possibile usare qualsiasi espressione che restituisca un bool
in una clausola when
(tranne attendere ). La variabile Exception dichiarata ex
è accessibile dall'interno della clausola when
:
var SqlErrorToIgnore = 123;
try
{
DoSQLOperations();
}
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore)
{
throw new Exception("An error occurred accessing the database", ex);
}
È possibile combinare più blocchi catch
con clausole when
. Il primo when
clausola che restituisce true
causerà l'intercettazione dell'eccezione. Il suo blocco di catch
verrà inserito, mentre le altre clausole di catch
verranno ignorate (le loro clausole when
non saranno valutate). Per esempio:
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
{ ... }
Risky quando la clausola
Attenzione
Può essere rischioso utilizzare filtri di eccezione: quando l'
Exception
viene lanciata dall'interno delwhen
la clausola, l'Exception
dalwhen
clausola viene ignorato e viene trattato comefalse
. Questo approccio consente agli sviluppatori di scriverewhen
clausola senza occuparsi di casi non validi.
Il seguente esempio illustra un tale scenario:
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);
}
Si noti che i filtri delle eccezioni evitano i problemi con il numero di riga confusi associati all'uso di throw
quando il codice fallito si trova all'interno della stessa funzione. Ad esempio in questo caso il numero di riga è riportato come 6 anziché 3:
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) {
6. throw;
7. }
Il numero di riga di eccezione è segnalato come 6 perché l'errore è stato rilevato e ri-generato con l'istruzione throw
sulla riga 6.
Lo stesso non succede con i filtri delle eccezioni:
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) when (a != 0) {
6. throw;
7. }
In questo esempio a
è 0 quindi la clausola catch
viene ignorata ma 3 viene riportato come numero di riga. Questo perché non si srotolano lo stack . Più specificamente, l'eccezione non viene rilevata sulla linea 5 perché a
infatti fa uguale 0
e quindi non v'è alcuna possibilità per l'eccezione di essere ri-gettato sulla linea 6 perché la linea 6 non viene eseguito.
Registrazione come effetto collaterale
Le chiamate al metodo nella condizione possono causare effetti collaterali, pertanto i filtri delle eccezioni possono essere utilizzati per eseguire codice su eccezioni senza rilevarli. Un esempio comune che sfrutta questo è un metodo Log
che restituisce sempre false
. Ciò consente di tracciare le informazioni del registro durante il debug senza la necessità di ripetere l'eccezione.
Tieni presente che sebbene questo sembra essere un modo comodo di registrazione, può essere rischioso, specialmente se vengono utilizzati assembly di logging di terze parti. Questi potrebbero generare eccezioni durante l'accesso a situazioni non ovvie che potrebbero non essere rilevate facilmente (vedere Risky
when(...)
clausola precedente).
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;
}
L'approccio comune nelle versioni precedenti di C # era di registrare e rilanciare l'eccezione.
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);
}
Il blocco finally
Il blocco finally
viene eseguito ogni volta che l'eccezione viene lanciata o meno. Una sottigliezza con le espressioni in when
filtri delle eccezioni vengono eseguiti più in alto nello stack prima di entrare nei blocchi finally
interni. Ciò può causare risultati e comportamenti imprevisti quando il codice tenta di modificare lo stato globale (come l'utente o la cultura del thread corrente) e reimpostarlo in un blocco finally
.
Esempio: finally
block
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");
}
}
Uscita prodotta:
Inizio
Valutazioni: True
Infine interiore
Catturare
Finalmente esterno
Nell'esempio precedente, se il metodo SomeOperation
non desidera "perdere" lo stato globale cambia in clausole when
del chiamante, dovrebbe anche contenere un blocco catch
per modificare lo stato. Per esempio:
private static void SomeOperation()
{
try
{
Flag = true;
throw new Exception("Boom");
}
catch
{
Flag = false;
throw;
}
finally
{
Flag = false;
Console.WriteLine("Inner Finally");
}
}
È anche comune vedere classi di helper IDisposable
sfruttano la semantica dell'utilizzo di blocchi per raggiungere lo stesso obiettivo, poiché IDisposable.Dispose
verrà sempre chiamato prima che un'eccezione chiamata all'interno di un blocco using
inizi a scoppiare nello stack.
Inizializzatori di proprietà automatica
introduzione
Le proprietà possono essere inizializzate con l'operatore =
dopo la chiusura }
. La classe Coordinate
seguito mostra le opzioni disponibili per inizializzare una proprietà:
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
}
Accessors con diversa visibilità
È possibile inizializzare le proprietà automatiche che hanno visibilità diversa sui loro accessor. Ecco un esempio con un setter protetto:
public string Name { get; protected set; } = "Cheeze";
L'accessor può anche essere internal
, internal protected
o private
.
Proprietà di sola lettura
Oltre alla flessibilità con la visibilità, puoi anche inizializzare le proprietà automatiche di sola lettura. Ecco un esempio:
public List<string> Ingredients { get; } =
new List<string> { "dough", "sauce", "cheese" };
Questo esempio mostra anche come inizializzare una proprietà con un tipo complesso. Inoltre, le proprietà automatiche non possono essere solo di scrittura, quindi preclude anche l'inizializzazione di sola scrittura.
Vecchio stile (pre C # 6.0)
Prima del C # 6, questo richiedeva un codice molto più dettagliato. Stavamo usando una variabile extra chiamata backing property per la proprietà per dare un valore predefinito o per inizializzare la proprietà pubblica come di seguito,
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: prima di C # 6.0, era ancora possibile inizializzare le proprietà di auto-implementazione di lettura e scrittura (proprietà con getter e setter) all'interno del costruttore, ma non era possibile inizializzare la proprietà in linea con la sua dichiarazione
uso
Gli inizializzatori devono valutare le espressioni statiche, proprio come gli inizializzatori di campo. Se è necessario fare riferimento a membri non statici, è possibile inizializzare le proprietà in costruttori come in precedenza oppure utilizzare proprietà con corpo di espressione. Le espressioni non statiche, come quella seguente (commentate), generano un errore del compilatore:
// public decimal X { get; set; } = InitMe(); // generates compiler error
decimal InitMe() { return 4m; }
Ma i metodi statici possono essere utilizzati per inizializzare le proprietà automatiche:
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;
}
}
Questo metodo può essere applicato anche a proprietà con diversi livelli di accesso:
public short Type { get; private set; } = 15;
L'inizializzatore della proprietà automatica consente l'assegnazione di proprietà direttamente all'interno della dichiarazione. Per le proprietà di sola lettura, si prende cura di tutti i requisiti richiesti per garantire che la proprietà sia immutabile. Si consideri, ad esempio, la classe FingerPrint
nel seguente esempio:
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;
}
Note cautelative
Fai attenzione a non confondere gli inizializzatori di auto-proprietà o di campo con metodi di espressione del corpo simili che utilizzano =>
anziché =
, e campi che non includono { get; }
.
Ad esempio, ciascuna delle seguenti dichiarazioni è diversa.
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>();
}
Manca { get; }
nella dichiarazione della proprietà risulta in un campo pubblico. Sia gli utenti di proprietà di Users1
lettura Users1
che quelli di lettura-scrittura Users2
vengono inizializzati solo una volta, ma un campo pubblico consente di modificare l'istanza di raccolta dall'esterno della classe, che di solito non è desiderabile. La modifica di una proprietà automatica di sola lettura con il corpo di un'espressione in proprietà di sola lettura con l'inizializzatore richiede non solo la rimozione di >
da =>
, ma l'aggiunta di { get; }
.
Il diverso simbolo ( =>
invece di =
) in Users3
risulta in ogni accesso alla proprietà che restituisce una nuova istanza di HashSet<UserDto>
che, mentre C # valido (dal punto di vista del compilatore) è improbabile che sia il comportamento desiderato quando usato per un membro della collezione.
Il codice sopra è 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>(); } }
}
Inizializzatori dell'indice
Gli inizializzatori dell'indice consentono di creare e inizializzare oggetti con indici contemporaneamente.
Ciò semplifica l'inizializzazione dei dizionari:
var dict = new Dictionary<string, int>()
{
["foo"] = 34,
["bar"] = 42
};
Qualsiasi oggetto che abbia un getter o setter indicizzato può essere usato con questa sintassi:
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();
}
}
Produzione:
Indice: foo, valore: 34
Indice: bar, valore: 42
Se la classe ha più indicizzatori è possibile assegnarli tutti in un singolo gruppo di istruzioni:
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"
};
}
}
Produzione:
Indice: foo, valore: 34
Indice: bar, valore: 42
Indice: 10, valore: dieci
Indice: 42, valore: significato della vita
Va notato che l'accessore del set
dell'indicizzatore potrebbe comportarsi in modo diverso rispetto a un metodo Add
(utilizzato negli inizializzatori della raccolta).
Per esempio:
var d = new Dictionary<string, int>
{
["foo"] = 34,
["foo"] = 42,
}; // does not throw, second value overwrites the first one
contro:
var d = new Dictionary<string, int>
{
{ "foo", 34 },
{ "foo", 42 },
}; // run-time ArgumentException: An item with the same key has already been added.
Interpolazione a stringa
L'interpolazione delle stringhe consente allo sviluppatore di combinare variables
e testo per formare una stringa.
Esempio di base
Vengono create due variabili int
: foo
e bar
.
int foo = 34;
int bar = 42;
string resultString = $"The foo is {foo}, and the bar is {bar}.";
Console.WriteLine(resultString);
Uscita :
Il foo è 34 e la barra 42.
Le bretelle all'interno delle stringhe possono ancora essere utilizzate, come questa:
var foo = 34;
var bar = 42;
// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");
Questo produce il seguente risultato:
Il foo è {pippo} e la barra è {bar}.
Utilizzo dell'interpolazione con letterali stringa letterali
Usando @
prima della stringa, la stringa verrà interpretata letteralmente. Quindi, ad esempio, i caratteri Unicode o le interruzioni di riga rimarranno esattamente come sono stati digitati. Tuttavia, questo non influirà sulle espressioni in una stringa interpolata come mostrato nell'esempio seguente:
Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
Produzione: Nel caso non fosse chiaro:
\ u00B9
Il pippo
è 34,
e il bar
è 42.
espressioni
Con l'interpolazione delle stringhe, è possibile valutare anche le espressioni all'interno di parentesi graffe {}
. Il risultato verrà inserito nella posizione corrispondente all'interno della stringa. Ad esempio, per calcolare il massimo di foo
e bar
e inserirlo, utilizzare Math.Max
tra parentesi graffe:
Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");
Produzione:
E il più grande è: 42
Nota: tutti gli spazi bianchi iniziali o finali (inclusi spazio, scheda e CRLF / nuova riga) tra la parentesi graffa e l'espressione vengono completamente ignorati e non inclusi nell'output
Come altro esempio, le variabili possono essere formattate come valuta:
Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");
Produzione:
Foo formattato come valuta a 4 cifre decimali: $ 34,0000
Oppure possono essere formattati come date:
Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");
Produzione:
Oggi è: lunedì 20 luglio 2015
Le dichiarazioni con un operatore condizionale (Ternario) possono anche essere valutate all'interno dell'interpolazione. Tuttavia, questi devono essere racchiusi tra parentesi, in quanto i due punti vengono utilizzati per indicare la formattazione come mostrato sopra:
Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");
Produzione:
Bar è più grande di foo!
Le espressioni condizionali e gli identificatori di formato possono essere mescolati:
Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");
Produzione:
Ambiente: processo a 32 bit
Sequenze di fuga
L'escape dei caratteri barra rovesciata ( \
) e virgola ( "
) funziona esattamente nello stesso modo nelle stringhe interpolate come nelle stringhe non interpolate, sia per i letterali stringa letterale che per quelli non verbali:
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 \");
Produzione:
Foo è 34. In una stringa non-letterale, dobbiamo uscire da "e \ con barre retroverse.
Foo è 34. In una stringa letterale, dobbiamo scappare "con una citazione extra, ma non abbiamo bisogno di scappare \
Per includere una parentesi graffa {
o }
in una stringa interpolata, utilizzare due parentesi graffe {{
o }}
:
$"{{foo}} is: {foo}"
Produzione:
{foo} è: 34
FormattableString type
Il tipo di $"..."
espressione di interpolazione della stringa non è sempre una stringa semplice. Il compilatore decide quale tipo assegnare a seconda del contesto:
string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";
Questo è anche l'ordine delle preferenze di tipo quando il compilatore deve scegliere quale metodo di overload verrà chiamato.
Un nuovo tipo , System.FormattableString
, rappresenta una stringa di formato composita, insieme agli argomenti da formattare. Usalo per scrivere applicazioni che gestiscono in modo specifico gli argomenti di interpolazione:
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();
// ...
}
Chiama il metodo sopra con:
AddLogItem($"The foo is {foo}, and the bar is {bar}.");
Ad esempio, si potrebbe scegliere di non sostenere il costo delle prestazioni della formattazione della stringa se il livello di registrazione stava già filtrando l'elemento del registro. Conversioni implicite
Esistono conversioni di tipo implicite da una stringa interpolata:
var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
Puoi anche produrre una variabile IFormattable
che ti permette di convertire la stringa con un contesto invariante: var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";
Metodi di coltura attuali e invariabili
Se l'analisi del codice è attivata, tutte le stringhe interpolate generano l'avviso CA1305 (Specifica IFormatProvider
). Un metodo statico può essere utilizzato per applicare la cultura corrente.
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);
}
}
Quindi, per produrre una stringa corretta per la cultura corrente, basta usare l'espressione:
Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
Nota : Current
e Invariant
non possono essere creati come metodi di estensione perché, per impostazione predefinita, il compilatore assegna il tipo String
all'espressione di stringa interpolata che non riesce a compilare il seguente codice: $"interpolated {typeof(string).Name} string.".Current();
FormattableString
classe FormattableString
contiene già il metodo Invariant()
, quindi il modo più semplice per passare alla cultura invariante è affidarsi using static
:
using static System.FormattableString;
string invariant = Invariant($"Now = {DateTime.Now}"); string current = $"Now = {DateTime.Now}";
Dietro le quinte
Le stringhe interpolate sono solo uno zucchero sintattico per String.Format()
. Il compilatore ( Roslyn ) lo trasformerà in un String.Format
dietro le quinte:
var text = $"Hello {name + lastName}";
Quanto sopra sarà convertito in qualcosa di simile a questo:
string text = string.Format("Hello {0}", new object[] {
name + lastName
});
Interpolazione a stringa e Linq
È possibile utilizzare stringhe interpolate nelle istruzioni Linq per aumentare ulteriormente la leggibilità.
var fooBar = (from DataRow x in fooBarTable.Rows
select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();
Può essere riscritto come:
var fooBar = (from DataRow x in fooBarTable.Rows
select $"{x["foo"]}{x["bar"]}").ToList();
Stringhe interpolate riutilizzabili
Con string.Format
, puoi creare stringhe di formato riutilizzabili:
public const string ErrorFormat = "Exception caught:\r\n{0}";
// ...
Logger.Log(string.Format(ErrorFormat, ex));
Le stringhe interpolate, tuttavia, non verranno compilate con i segnaposto che fanno riferimento a variabili inesistenti. Quanto segue non verrà compilato:
public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context
Invece, crea un Func<>
che consuma variabili e restituisce una String
:
public static Func<Exception, string> FormatError =
error => $"Exception caught:\r\n{error}";
// ...
Logger.Log(FormatError(ex));
Interpolazione e localizzazione delle stringhe
Se stai localizzando la tua applicazione, potresti chiederti se è possibile utilizzare l'interpolazione delle stringhe insieme alla localizzazione. In effetti, sarebbe bello avere la possibilità di memorizzare nel file di risorse String
s come:
"My name is {name} {middlename} {surname}"
invece del molto meno leggibile: "My name is {0} {1} {2}"
String
processo di interpolazione delle String
verifica in fase di compilazione , diversamente dalla stringa di formattazione con string.Format
che si verifica in fase di runtime . Le espressioni in una stringa interpolata devono fare riferimento a nomi nel contesto corrente e devono essere archiviate in file di risorse. Ciò significa che se vuoi usare la localizzazione devi farlo come:
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);
Se le stringhe di risorse per le lingue utilizzate in precedenza sono archiviate correttamente nei singoli file di risorse, è necessario ottenere il seguente output:
Bonjour, mon nom est John
Ciao, mein Nome ist John
Ciao il mio nome è John
Nota che ciò implica che il nome segue la stringa localizzata in ogni lingua. In caso contrario, è necessario aggiungere segnaposti alle stringhe di risorse e modificare la funzione in alto oppure è necessario interrogare le informazioni sulla cultura nella funzione e fornire un'istruzione switch case contenente i diversi casi. Per ulteriori dettagli sui file di risorse, vedi Come utilizzare la localizzazione in C # .
È buona norma utilizzare una lingua di fallback predefinita che la maggior parte delle persone capirà, nel caso in cui una traduzione non sia disponibile. Suggerisco di usare l'inglese come lingua di fallback predefinita.
Interpolazione ricorsiva
Sebbene non sia molto utile, è consentito utilizzare una string
interpolata ricorsivamente all'interno delle parentesi graffe di un'altra:
Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:");
Console.WriteLine($"My class is called {nameof(MyClass)}.");
Produzione:
La stringa ha 27 caratteri:
La mia classe si chiama MyClass.
Aspettare e prendere
È possibile utilizzare await
espressione Attendi per applicare l'operatore Attendi a Attività o Attività (di TResult) nel catch
e finally
blocchi in C # 6.
Non è stato possibile utilizzare l'espressione await
nel catch
e finally
blocchi nelle versioni precedenti a causa dei limiti del compilatore. C # 6 rende molto più semplice l'attesa di compiti asincroni consentendo l'espressione di 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();
}
È stato richiesto in C # 5 di usare un bool
o dichiarare Exception
al di fuori del try catch per eseguire operazioni asincrone. Questo metodo è mostrato nel seguente esempio:
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();
Propagazione nulla
Il ?.
operatore e operatore ?[...]
sono chiamati operatori condizionali nulli . A volte viene anche indicato con altri nomi come l' operatore di navigazione sicura .
Questo è utile, perché se il .
L'operatore (accessor membro) viene applicato a un'espressione che restituisce null
, il programma genererà NullReferenceException
. Se lo sviluppatore utilizza invece il ?.
(null-condizionale), l'espressione valuterà a null invece di generare un'eccezione.
Si noti che se il ?.
operatore viene utilizzato e l'espressione non è nullo, ?.
e .
sono equivalenti.
Nozioni di base
var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null
Se l' classroom
non ha un insegnante, GetTeacher()
può restituire null
. Quando è null
e si accede alla proprietà Name
, verrà generata NullReferenceException
.
Se modifichiamo questa affermazione per usare il ?.
sintassi, il risultato dell'intera espressione sarà null
:
var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null
Successivamente, se la classroom
potrebbe anche essere null
, potremmo scrivere anche questa affermazione come:
var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null
Questo è un esempio di cortocircuito: quando qualsiasi operazione di accesso condizionale che utilizza l'operatore null-condizionale restituisce null, l'intera espressione restituisce immediatamente null, senza elaborare il resto della catena.
Quando il membro terminale di un'espressione che contiene l'operatore condizionale nullo è di un tipo valore, l'espressione Nullable<T>
un Nullable<T>
di quel tipo e quindi non può essere utilizzato come sostituzione diretta dell'espressione senza ?.
.
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
Utilizzare con l'operatore Null-Coalescing (??)
È possibile combinare l'operatore null-condizionale con l'operatore Null-coalescing ( ??
) per restituire un valore predefinito se l'espressione si risolve in null
. Usando il nostro esempio sopra:
var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher()
// returns null OR classroom is null OR Name is null
Utilizzare con gli indicizzatori
L'operatore null-condizionale può essere utilizzato con gli indicizzatori :
var firstStudentName = classroom?.Students?[0]?.Name;
Nell'esempio sopra:
- Il primo
?.
assicura che l'classroom
non sianull
. - Il secondo
?
assicura che l'intera raccoltaStudents
non sianull
. - Il terzo
?.
dopo che l'indicizzatore assicura che l'indicizzatore[0]
non ha restituito un oggettonull
. Va notato che questa operazione può ancora lanciare unIndexOutOfRangeException
.
Utilizzare con funzioni void
L'operatore Null-condition può anche essere utilizzato con le funzioni void
. Tuttavia, in questo caso, la dichiarazione non valuterà null
. NullReferenceException
solo una NullReferenceException
.
List<string> list = null;
list?.Add("hi"); // Does not evaluate to null
Utilizzare con invocazione di eventi
Supponendo la seguente definizione di evento:
private event EventArgs OnCompleted;
Quando si invoca un evento, tradizionalmente, è consigliabile verificare se l'evento è null
nel caso in cui non siano presenti abbonati:
var handler = OnCompleted;
if (handler != null)
{
handler(EventArgs.Empty);
}
Poiché è stato introdotto l'operatore null-condizionale, l'invocazione può essere ridotta a una singola riga:
OnCompleted?.Invoke(EventArgs.Empty);
limitazioni
L'operatore Null-condition produce rvalue, non lvalue, cioè non può essere utilizzato per l'assegnazione di proprietà, la sottoscrizione di eventi ecc. Ad esempio, il seguente codice non funzionerà:
// 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
Nota che:
int? nameLength = person?.Name.Length; // safe if 'person' is null
non è la stessa di:
int? nameLength = (person?.Name).Length; // avoid this
perché il primo corrisponde a:
int? nameLength = person != null ? (int?)person.Name.Length : null;
e quest'ultimo corrisponde a:
int? nameLength = (person != null ? person.Name : null).Length;
Nonostante l'operatore ternario ?:
Qui viene utilizzato per spiegare la differenza tra due casi, questi operatori non sono equivalenti. Questo può essere facilmente dimostrato con il seguente esempio:
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;
}
}
}
Quali uscite:
Propagazione nulla
Sono stato letto
0
Ternario
Sono stato letto
Sono stato letto
0
Per evitare invocazioni multiple equivalenti sarebbe:
var interimResult = foo.Bar;
Console.WriteLine(interimResult != null ? interimResult.Length : (int?)null);
E questa differenza spiega in qualche modo perché l'operatore di propagazione null non è ancora supportato negli alberi di espressione.
Uso del tipo statico
L' using static [Namespace.Type]
direttiva using static [Namespace.Type]
consente l'importazione di membri statici di tipi e valori di enumerazione. I metodi di estensione vengono importati come metodi di estensione (da un solo tipo), non in ambito di livello superiore.
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));
}
}
Migliore risoluzione del sovraccarico
Lo snippet seguente mostra un esempio di passaggio di un gruppo di metodi (al contrario di un lambda) quando è previsto un delegato. Risoluzione di sovraccarico ora risolverà questo invece di sollevare un ambiguo errore di sovraccarico dovuto alla capacità di C # 6 di controllare il tipo di ritorno del metodo che è stato passato.
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;
}
}
risultati:
Errore
errore CS0121: la chiamata è ambigua tra i seguenti metodi o proprietà: "Program.Overloaded (System.Action)" e "Program.Overloaded (System.Func)"
C # 6 può anche gestire bene il seguente caso di corrispondenza esatta per espressioni lambda che avrebbe comportato un errore in 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);
}
}
Cambiamenti minori e correzioni di errori
Le parentesi ora sono vietate attorno ai parametri denominati. Il seguente compila in C # 5, ma non C # 6
Console.WriteLine((value: 23));
L'operando di is
e as
non è più possibile essere gruppi di metodi. Il seguente compila in C # 5, ma non C # 6
var result = "".Any is byte;
Il compilatore nativo ha permesso questo (anche se ha mostrato un avvertimento), e in effetti non ha nemmeno controllato la compatibilità del metodo di estensione, permettendo cose pazzesche come
1.Any is string
oIDisposable.Dispose is object
.
Vedi questo riferimento per gli aggiornamenti sulle modifiche.
Utilizzo di un metodo di estensione per l'inizializzazione della raccolta
La sintassi di inizializzazione della raccolta può essere utilizzata durante l'istanziazione di qualsiasi classe che implementa IEnumerable
e ha un metodo denominato Add
che accetta un singolo parametro.
Nelle versioni precedenti, questo metodo Add
doveva essere un metodo di istanza sulla classe da inizializzare. In C # 6, può anche essere un metodo di estensione.
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
}
}
Questo produrrà:
Elemento aggiunto con metodo di aggiunta istanza: 1
Elemento aggiunto con metodo di aggiunta istanza: 2
Elemento aggiunto con metodo di aggiunta istanza: 3
Elemento aggiunto con metodo di aggiunta estensione: 4
Elemento aggiunto con il metodo di aggiunta estensione: 5
Elemento aggiunto con il metodo di aggiunta estensione: 6
Disabilita i miglioramenti degli avvisi
In C # 5.0 e precedenti lo sviluppatore poteva solo sopprimere gli avvertimenti per numero. Con l'introduzione di Roslyn Analyzer, C # ha bisogno di un modo per disabilitare gli avvisi emessi da librerie specifiche. Con C # 6.0 la direttiva pragma può sopprimere gli avvertimenti per nome.
Prima:
#pragma warning disable 0501
C # 6.0:
#pragma warning disable CS0501