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:

6.0

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.

Visualizza la demo


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 del when la clausola, l' Exception dal when clausola viene ignorato e viene trattato come false . Questo approccio consente agli sviluppatori di scrivere when 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);
}

Visualizza la demo

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;
}

Visualizza la demo

L'approccio comune nelle versioni precedenti di C # era di registrare e rilanciare l'eccezione.

6.0
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);
}

Visualizza la demo


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

Visualizza la demo

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à:

6.0
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,

6.0
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

Visualizza la demo


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;
}

Visualizza la demo


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

Visualizza la demo

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.

Visualizza la demo

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.

Visualizza la demo


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

Visualizza la demo

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

Visualizza la demo

Oppure possono essere formattati come date:

Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");

Produzione:

Oggi è: lunedì 20 luglio 2015

Visualizza la demo

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!

Visualizza la demo

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

Visualizza la demo


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

Visualizza la demo

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

Visualizza la demo

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

Visualizza la demo

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 sia null .
  • Il secondo ? assicura che l'intera raccolta Students non sia null .
  • Il terzo ?. dopo che l'indicizzatore assicura che l'indicizzatore [0] non ha restituito un oggetto null . Va notato che questa operazione può ancora lanciare un IndexOutOfRangeException .

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

Visualizza la demo

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.

6.0
using static System.Console;
using static System.ConsoleColor;
using static System.Math;

class Program
{
    static void Main()
    {
        BackgroundColor = DarkBlue;
        WriteLine(Sqrt(2));
    }
}

Live Demo Fiddle

6.0
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:

6.0

Produzione

sovraccarico con Func <int> chiamato

Visualizza la demo

5.0

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

5.0
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

5.0
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 o IDisposable.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


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow