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.Math
ad 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 null
blocco 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 Dictionary
di 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.