Panoramica delle nuove funzionalità di C# 6

La versione 6 del linguaggio C# continua a evolvere il linguaggio in modo da avere meno codice boilerplate, maggiore chiarezza e maggiore coerenza. Sintassi di inizializzazione più pulita, possibilità di usare await in blocchi catch/finally e condizionale null? l'operatore è particolarmente utile.

Nota

Per informazioni sulla versione più recente del linguaggio C# versione 7, vedere l'articolo Novità di C# 7.0

Questo documento presenta le nuove funzionalità di C# 6. È completamente supportato dal compilatore Mono e gli sviluppatori possono iniziare a usare le nuove funzionalità in tutte le piattaforme di destinazione Xamarin.

Novità del video C# 6

Uso di C# 6

Il compilatore C# 6 viene usato in tutte le versioni recenti di Visual Studio per Mac. Gli utenti che usano i compilatori della riga di comando devono confermare che mcs --version restituisce 4.0 o versione successiva. Visual Studio per Mac gli utenti possono verificare se è installato Mono 4 (o versione successiva) facendo riferimento a Informazioni su Visual Studio per Mac Visual Studio per Mac >> Mostra dettagli.

Meno Boilerplate

using static

Le enumerazioni e alcune classi, System.Mathad esempio , sono principalmente titolari di valori e funzioni statici. In C# 6 è possibile importare tutti i membri statici di un tipo con una singola using static istruzione. Confrontare una funzione trigonometrica tipica in C# 5 e C# 6:

// Classic C#
class MyClass
{
    public static Tuple<double,double> SolarAngleOld(double latitude, double declination, double hourAngle)
    {
        var tmp = Math.Sin (latitude) * Math.Sin (declination) + Math.Cos (latitude) * Math.Cos (declination) * Math.Cos (hourAngle);
        return Tuple.Create (Math.Asin (tmp), Math.Acos (tmp));
    }
}

// C# 6
using static System.Math;

class MyClass
{
    public static Tuple<double, double> SolarAngleNew(double latitude, double declination, double hourAngle)
    {
        var tmp = Asin (latitude) * Sin (declination) + Cos (latitude) * Cos (declination) * Cos (hourAngle);
        return Tuple.Create (Asin (tmp), Acos (tmp));
    }
}

using static non rende i campi pubblici const , ad esempio Math.PI e Math.E, accessibili direttamente:

for (var angle = 0.0; angle <= Math.PI * 2.0; angle += Math.PI / 8) ... 
//PI is const, not static, so requires Math.PI

uso statico con metodi di estensione

La using static struttura funziona in modo leggermente diverso con i metodi di estensione. Anche se i metodi di estensione vengono scritti usando static, non hanno senso senza un'istanza su cui operare. Pertanto, quando using static viene usato con un tipo che definisce i metodi di estensione, i metodi di estensione diventano disponibili nel tipo di destinazione (tipo del this metodo). Ad esempio, using static System.Linq.Enumerable può essere usato per estendere l'API degli IEnumerable<T> oggetti senza inserire tutti i tipi LINQ:

using static System.Linq.Enumerable;
using static System.String;

class Program
{
    static void Main()
    {
        var values = new int[] { 1, 2, 3, 4 };
        var evenValues = values.Where (i => i % 2 == 0);
        System.Console.WriteLine (Join(",", evenValues));
    }
}

Nell'esempio precedente viene illustrata la differenza di comportamento: il metodo Enumerable.Where di estensione è associato alla matrice, mentre il metodo String.Join statico può essere chiamato senza riferimento al String tipo.

espressioni nameof

In alcuni casi, si vuole fare riferimento al nome assegnato a una variabile o a un campo. In C# 6 nameof(someVariableOrFieldOrType) restituirà la stringa "someVariableOrFieldOrType". Ad esempio, quando si genera un'eccezione ArgumentException è molto probabile che si voglia assegnare un nome all'argomento non valido:

throw new ArgumentException ("Problem with " + nameof(myInvalidArgument))

Il vantaggio principale delle nameof espressioni è che sono tipizzato e sono compatibili con il refactoring basato su strumenti. Il controllo dei tipi di nameof espressioni è particolarmente utile nelle situazioni in cui un string oggetto viene usato per associare in modo dinamico i tipi. Ad esempio, in iOS un string oggetto viene usato per specificare il tipo usato per creare UITableViewCell prototipi di oggetti in un oggetto UITableView. nameof in grado di garantire che questa associazione non ha esito negativo a causa di un refactoring di ortografia o sloppy:

public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
    var cell = tableView.DequeueReusableCell (nameof(CellTypeA), indexPath);
    cell.TextLabel.Text = objects [indexPath.Row].ToString ();
    return cell;
}

Sebbene sia possibile passare un nome completo a nameof, viene restituito solo l'elemento finale (dopo l'ultimo .). Ad esempio, è possibile aggiungere un data binding in Xamarin.Forms:

var myReactiveInstance = new ReactiveType ();
var myLabelOld.BindingContext = myReactiveInstance;
var myLabelNew.BindingContext = myReactiveInstance;
var myLabelOld.SetBinding (Label.TextProperty, "StringField");
var myLabelNew.SetBinding (Label.TextProperty, nameof(ReactiveType.StringField));

Le due chiamate a SetBinding passano valori identici: nameof(ReactiveType.StringField) è "StringField", non "ReactiveType.StringField" come previsto inizialmente.

Operatore condizionale Null

Gli aggiornamenti precedenti di C# hanno introdotto i concetti relativi ai tipi nullable e all'operatore ?? null-coalescing per ridurre la quantità di codice boilerplate quando si gestiscono valori nullable. C# 6 continua questo tema con l'operatore "condizionale null" ?.. Se utilizzato su un oggetto sul lato destro di un'espressione, l'operatore condizionale Null restituisce il valore del membro se l'oggetto non null è e null in caso contrario:

var ss = new string[] { "Foo", null };
var length0 = ss [0]?.Length; // 3
var length1 = ss [1]?.Length; // null
var lengths = ss.Select (s => s?.Length ?? 0); //[3, 0]

(Sia length0 che length1 sono dedotti per essere di tipo int?)

L'ultima riga nell'esempio precedente mostra l'operatore ? condizionale Null in combinazione con l'operatore ?? null-coalescing. Il nuovo operatore condizionale Null C# 6 restituisce null l'elemento 2nd della matrice, a questo punto l'operatore null-coalescing viene attivato e fornisce un valore 0 alla lengths matrice ,indipendentemente dal fatto che sia appropriato o meno sia, naturalmente, specifico del problema.

L'operatore condizionale Null dovrebbe ridurre enormemente la quantità di controllo null boilerplate necessaria in molte applicazioni.

Esistono alcune limitazioni per l'operatore condizionale Null a causa di ambiguità. Non è possibile seguire immediatamente un oggetto ? con un elenco di argomenti racchiusi tra parentesi, come si potrebbe sperare di eseguire con un delegato:

SomeDelegate?("Some Argument") // Not allowed

Tuttavia, Invoke può essere usato per separare l'oggetto ? dall'elenco di argomenti ed è ancora un miglioramento marcato rispetto a un nullblocco di controllo boilerplate:

public event EventHandler HandoffOccurred;
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    HandoffOccurred?.Invoke (this, userActivity.UserInfo);
    return true;
}

Interpolazione di stringhe

La String.Format funzione usa tradizionalmente gli indici come segnaposto nella stringa di formato, ad esempio String.Format("Expected: {0} Received: {1}.", expected, received. Naturalmente, l'aggiunta di un nuovo valore ha sempre comportato un fastidioso piccolo compito di conteggiare gli argomenti, numerare i segnaposto e inserire il nuovo argomento nella sequenza destra nell'elenco di argomenti.

La nuova funzionalità di interpolazione di stringhe di C# 6 migliora notevolmente su String.Format. È ora possibile denominare direttamente le variabili in una stringa preceduta da un oggetto $. Ad esempio:

$"Expected: {expected} Received: {received}."

Le variabili sono, naturalmente, controllate e una variabile non disponibile o con errori di ortografia causerà un errore del compilatore.

I segnaposto non devono essere variabili semplici, ma possono essere espressioni. All'interno di questi segnaposto, è possibile usare le virgolette senza escapingere tali virgolette. Ad esempio, si noti quanto "s" segue:

var s = $"Timestamp: {DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}"

L'interpolazione di stringhe supporta la sintassi di allineamento e formattazione di String.Format. Proprio come è stato scritto {index, alignment:format}in precedenza, in C# 6 si scrive {placeholder, alignment:format}:

using static System.Linq.Enumerable;
using System;

class Program
{
    static void Main ()
    {
        var values = new int[] { 1, 2, 3, 4, 12, 123456 };
        foreach (var s in values.Select (i => $"The value is { i,10:N2}.")) {
            Console.WriteLine (s);
        }
    Console.WriteLine ($"Minimum is { values.Min(i => i):N2}.");
    }
}

il risultato è il seguente:

The value is       1.00.
The value is       2.00.
The value is       3.00.
The value is       4.00.
The value is      12.00.
The value is 123,456.00.
Minimum is 1.00.

L'interpolazione di stringhe è lo zucchero sintattico per String.Format: non può essere usato con @"" valori letterali stringa e non è compatibile con const, anche se non vengono utilizzati segnaposto:

const string s = $"Foo"; //Error : const requires value

Nel caso d'uso comune degli argomenti della funzione di compilazione con l'interpolazione di stringhe, è comunque necessario prestare attenzione ai problemi di escape, codifica e impostazioni cultura. Le query SQL e URL sono, naturalmente, fondamentali per la purificazione. Come per String.Format, l'interpolazione di stringhe usa .CultureInfo.CurrentCulture L'uso CultureInfo.InvariantCulture di è un po' più wordy:

Thread.CurrentThread.CurrentCulture  = new CultureInfo ("de");
Console.WriteLine ($"Today is: {DateTime.Now}"); //"21.05.2015 13:52:51"
Console.WriteLine ($"Today is: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}"); //"05/21/2015 13:52:51"

Inizializzazione

C# 6 offre diversi modi concisi per specificare proprietà, campi e membri.

Inizializzazione della proprietà automatica

Le proprietà automatiche possono ora essere inizializzate nello stesso modo conciso dei campi. Le proprietà automatiche non modificabili possono essere scritte solo con un getter:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;

Nel costruttore è possibile impostare il valore di una proprietà automatica getter-only:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;
    public string Description { get; }

    public ToDo (string description)
    {
        this.Description = description; //Can assign (only in constructor!)
    }

Questa inizializzazione delle proprietà automatiche è sia una funzionalità di risparmio di spazio generale che una boon agli sviluppatori che desiderano sottolineare l'immutabilità nei loro oggetti.

Inizializzatori di indice.

C# 6 introduce gli inizializzatori di indice, che consentono di impostare sia la chiave che il valore nei tipi con un indicizzatore. In genere, si tratta di strutture Dictionarydi dati di tipo -style:

partial void ActivateHandoffClicked (WatchKit.WKInterfaceButton sender)
{
    var userInfo = new NSMutableDictionary {
        ["Created"] = NSDate.Now,
        ["Due"] = NSDate.Now.AddSeconds(60 * 60 * 24),
        ["Task"] = Description
    };
    UpdateUserActivity ("com.xamarin.ToDo.edit", userInfo, null);
    statusLabel.SetText ("Check phone");
}

Membri della funzione con corpo di espressione

Le funzioni lambda offrono diversi vantaggi, uno dei quali consente semplicemente di risparmiare spazio. Analogamente, i membri della classe con corpo di espressione consentono di esprimere funzioni di piccole dimensioni in modo più conciso rispetto a quanto possibile nelle versioni precedenti di C# 6.

I membri della funzione con corpo di espressione usano la sintassi della freccia lambda anziché la sintassi tradizionale del blocco:

public override string ToString () => $"{FirstName} {LastName}";

Si noti che la sintassi della freccia lambda non usa un oggetto esplicito return. Per le funzioni che restituiscono void, l'espressione deve essere anche un'istruzione :

public void Log(string message) => System.Console.WriteLine($"{DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}: {message}");

I membri con corpo di espressione sono ancora soggetti alla regola async supportata per i metodi ma non alle proprietà:

//A method, so async is valid
public async Task DelayInSeconds(int seconds) => await Task.Delay(seconds * 1000);
//The following property will not compile
public async Task<int> LeisureHours => await Task.FromResult<char> (DateTime.Now.DayOfWeek.ToString().First()) == 'S' ? 16 : 5;

Eccezioni

Non esistono due modi: la gestione delle eccezioni è difficile da risolvere. Le nuove funzionalità di C# 6 rendono la gestione delle eccezioni più flessibile e coerente.

Filtri eccezioni

Per definizione, le eccezioni si verificano in circostanze insolite e può essere molto difficile ragionare e scrivere codice su tutti i modi in cui può verificarsi un'eccezione di un particolare tipo. C# 6 introduce la possibilità di proteggere un gestore di esecuzione con un filtro valutato dal runtime. A tale scopo, aggiungere un when (bool) modello dopo la dichiarazione normale catch(ExceptionType) . Nell'esempio seguente, un filtro distingue un errore di analisi relativo al date parametro anziché altri errori di analisi.

public void ExceptionFilters(string aFloat, string date, string anInt)
{
    try
    {
        var f = Double.Parse(aFloat);
        var d = DateTime.Parse(date);
        var n = Int32.Parse(anInt);
    } catch (FormatException e) when (e.Message.IndexOf("DateTime") > -1) {
        Console.WriteLine ($"Problem parsing \"{nameof(date)}\" argument");
    } catch (FormatException x) {
        Console.WriteLine ("Problem parsing some other argument");
    }
}

await in catch... Infine...

Le async funzionalità introdotte in C# 5 sono state un game changer per il linguaggio. In C# 5 await non è consentito in catch e finally blocchi, dato il valore della async/await funzionalità. C# 6 rimuove questa limitazione, consentendo l'attesa coerente dei risultati asincroni tramite il programma, come illustrato nel frammento di codice seguente:

async void SomeMethod()
{
    try {
        //...etc...
    } catch (Exception x) {
        var diagnosticData = await GenerateDiagnosticsAsync (x);
        Logger.log (diagnosticData);
    } finally {
        await someObject.FinalizeAsync ();
    }
}

Riepilogo

Il linguaggio C# continua a evolversi per rendere gli sviluppatori più produttivi, promuovendo al tempo stesso procedure consigliate e strumenti di supporto. Questo documento ha fornito una panoramica delle nuove funzionalità del linguaggio in C# 6 e ha brevemente illustrato come vengono usate.