Il presente articolo è stato tradotto automaticamente.

C#

C# 6.0 nuovo e migliorato

Mark Michaelis

Sebbene c# 6.0 non è ancora completa, è in un punto dove ora le caratteristiche sono vicino viene finalizzato. Ci sono stati un certo numero di modifiche e miglioramenti apportati a c# 6.0 in CTP3 rilascio della prossima versione di Visual Studio, nome in codice "14", poiché l'articolo maggio 2014, "A c# 6.0 lingua anteprima" (msdn.microsoft.com/magazine/dn683793.aspx).

In questo articolo, sarò introdurre nuove funzionalità e fornire un aggiornamento sulle caratteristiche discusse in maggio. Ti mantengo anche un blog aggiornato completo che descrive gli aggiornamenti ogni funzionalità c# 6.0. Check it out a itl.tc/csharp6. Molti di questi esempi sono dalla prossima edizione del mio libro, "essenziale c# 6.0" (Addison-Wesley Professional).

Operatore null-condizionale

Anche gli sviluppatori .NET più recenti sono probabilmente familiarità con il NullReferenceException. Questa è un'eccezione che indica quasi sempre un bug perché lo sviluppatore non esegue controllo null sufficiente prima di richiamare un membro su un oggetto (null). Considerare il seguente esempio:

public static string Truncate(string value, int length)
{
  string result = value;
  if (value != null) // Skip empty string check for elucidation
  {
    result = value.Substring(0, Math.Min(value.Length, length));
  }
  return result;
}

Se non fosse per la verifica di valori null, il metodo getterebbe un'eccezione NullReferenceException. Anche se è semplice, dover controllare il parametro stringa null è piuttosto dettagliata. Spesso, è probabile che l'approccio dettagliato degli inutili data la frequenza del confronto. C# 6.0 include un nuovo operatore null-condizionale che consente di scrivere più brevemente questi controlli:

public static string Truncate(string value, int length)
{          
  return value?.Substring(0, Math.Min(value.Length, length));
}
[TestMethod]
public void Truncate_WithNull_ReturnsNull()
{
  Assert.AreEqual<string>(null, Truncate(null, 42));
}

Come viene illustrato il metodo di Truncate_WithNull_ReturnsNull, se infatti il valore dell'oggetto è null, l'operatore null-condizionale restituirà null. Questo pone la questione di che cosa accade quando l'operatore null-condizionale appare all'interno di una catena di chiamate, come nell'esempio seguente:

public static string AdjustWidth(string value, int length)
{
  return value?.Substring(0, Math.Min(value.Length, length)).PadRight(length);
}
[TestMethod]
public void AdjustWidth_GivenInigoMontoya42_ReturnsInigoMontoyaExtended()
{
  Assert.AreEqual<int>(42, AdjustWidth("Inigo Montoya", 42).Length);
}

Anche se la sottostringa viene chiamato tramite l'operatore condizionale di null e un valore null?.Sottostringa apparentemente potrebbe restituire null, il comportamento di lingua fa ciò che si vorrebbe. La chiamata a PadRight di cortocircuiti e immediatamente restituisce null, evitando l'errore di programmazione che altrimenti risulterebbero in un'eccezione NullReferenceException. Questo è un concetto noto come null-propagazione.

L'operatore null-condizionale controlla in modo condizionale per null prima di richiamare il metodo di destinazione e qualsiasi ulteriore metodo all'interno della catena di chiamata. Potenzialmente, questo potrebbe produrre un risultato sorprendente come nel testo dichiarazione?.Length.GetType.

Se il condizionale-null restituisce null quando il target di invocazione è null, che cosa è i dati risultanti tipo per una chiamata di un membro che restituisce un tipo di valore — dato un valore di tipo non può essere null? Ad esempio, il tipo di dati restituito dal valore?. Lunghezza non può essere semplicemente int. La risposta, naturalmente, è un nullable (int?). In realtà, un tentativo di assegnare il risultato semplicemente a int produrrà un errore di compilazione:

int length = text?.Length; // Compile Error: Cannot implicitly convert type 'int?' to 'int'

Null-condizionale ha due forme sintattiche. In primo luogo, è il punto interrogativo prima l'operatore punto (?.). Il secondo è quello di utilizzare il punto interrogativo in combinazione con l'operatore di indice. Ad esempio, dato un insieme, anziché controllare per null esplicitamente prima indicizzazione nell'insieme, è possibile farlo utilizzando l'operatore condizionale null:

public static IEnumerable<T> GetValueTypeItems<T>(
  IList<T> collection, params int[] indexes)
  where T : struct
{
  foreach (int index in indexes)
  {
    T? item = collection?[index];
    if (item != null) yield return (T)item;
  }
}

Questo esempio utilizza il modulo null-condizionale indice dell'operatore?[...], causando l'indicizzazione in collezione solo per verificare se la raccolta non è null. Con questa forma l'operatore null-condizionale, il T? Item = insieme?comportamentale, istruzione [index] equivale a:

T? item = (collection != null) ? collection[index] : null.

Si noti che l'operatore null-condizionale può recuperare solo gli elementi. Non funzionerà per assegnare un elemento. Cosa sarebbe dire che, dato un insieme di null, comunque?

Si noti l'ambiguità implicita quando si utilizza?[…] su un tipo di riferimento. Poiché i tipi di riferimento possono essere null, null derivare dalla?[…] operatore è ambiguo circa se la raccolta era null o l'elemento stesso è stato, infatti, null.

Un'applicazione particolarmente utile dell'operatore null-condizionale risolve un'idiosincrasia di c# che esiste dal c# 1.0 — controllo null prima di richiamare un delegato. Si consideri il codice c# 2.0 in Figura 1.

Figura 1 controllo null prima di richiamare un delegato

class Theremostat
{
  event EventHandler<float> OnTemperatureChanged;
  private int _Temperature;
  public int Temperature
  {
    get
    {
      return _Temperature;
    }
    set
    {
      // If there are any subscribers, then
      // notify them of changes in temperature
      EventHandler<float> localOnChanged =
        OnTemperatureChanged;
      if (localOnChanged != null)
      {
        _Temperature = value;
        // Call subscribers
        localOnChanged(this, value);
      }
    }
  }
}

Sfruttando l'operatore condizionale-null, l'intera implementazione impostata è ridotto a semplicemente:

OnTemperatureChanged?.Invoke(this, value)

Tutto ciò che serve ora è chiamata a Invoke preceduti da un operatore null-condizionale. Non è necessario assegnare all'istanza di delegato a una variabile locale al fine di essere thread-safe o addirittura a controllare in modo esplicito il valore null prima di richiamare il delegato.

Gli sviluppatori c# hanno domandato se questo sarebbe migliorato per le ultime quattro uscite. Finalmente sta per accadere. Questa caratteristica da sola cambierà il modo si richiama delegati.

Un altro modello comune dove l'operatore null-condizionale potrebbe essere prevalente è in combinazione con l'operatore di coalesce. Anziché controllare per null su linesOfCode prima di richiamare la lunghezza, è possibile scrivere un algoritmo di conteggio elemento come segue:

List<string> linesOfCode = ParseSourceCodeFile("Program.cs");
return linesOfCode?.Count ?? 0;

In questo caso, qualsiasi insieme vuoto (nessun oggetto) e una raccolta null sono entrambi normalizzati per restituire lo stesso conteggio. In sintesi, sarà l'operatore null-condizionale:

  • Restituire null se l'operando è null
  • Cortocircuito invocazioni aggiuntivi nella catena di chiamate se l'operando è null
  • Restituire un tipo nullable (System. Nullable < T >) se il membro di destinazione restituisce un tipo di valore
  • Supporto chiamata delegato in modo sicuro filo
  • È disponibile come sia un operatore membro (?.) e un operatore di indice (? […])

Inizializzatori di proprietà auto

Qualsiasi sviluppatore .NET che mai correttamente ha implementato una struttura senza dubbio è stato disturbato da quanto sintassi che serve per fare il tipo immutabile (come standard .NET suggeriscono che dovrebbe essere).  In questione è il fatto che dovrebbe avere una proprietà di sola lettura:

  1. Un campo di backup definito solo lettura
  2. Inizializzazione del campo all'interno del costruttore sostegno da
  3. Implementazione esplicita della proprietà (piuttosto che tramite una auto-proprietà)
  4. Un'implementazione esplicita di richiamo che restituisce il campo sottostante

Tutto questo è solo per "correttamente" implementare una proprietà immutabile. Questo comportamento viene poi ripetuto per tutte le proprietà del tipo. Così facendo la cosa giusta richiede uno sforzo significativamente maggiore rispetto l'approccio fragile. C# 6.0 viene in soccorso con una nuova funzionalità denominata inizializzatori di proprietà di auto (CTP3 include anche il supporto per le espressioni di inizializzazione). L'inizializzatore di proprietà auto permette di assegnazione delle proprietà direttamente all'interno della loro dichiarazione. Per le proprietà di sola lettura, si occupa di tutta la cerimonia necessaria a garantire che la proprietà non è modificabile. Si consideri, ad esempio, la classe FingerPrint in questo 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;
}

Come Mostra il codice, inizializzatori di proprietà consentono di assegnare la proprietà di un valore iniziale come parte della dichiarazione di proprietà. La proprietà può essere di sola lettura (solo un getter) o lettura/scrittura (setter e getter). Quando è in sola lettura, il campo sottostante sottostante è automaticamente dichiarato con il modificatore di sola lettura. Questo assicura che esso non è modificabile dopo l'inizializzazione.

Inizializzatori possono essere qualsiasi espressione. Sfruttando l'operatore condizionale, ad esempio, si può predefinito il valore di inizializzazione:

public string Config { get; } = string.IsNullOrWhiteSpace(
  string connectionString =
    (string)Properties.Settings.Default.Context?["connectionString"])?
  connectionString : "<none>";

In questo esempio, si noti l'utilizzo dell'espressione della dichiarazione (vedere itl.tc/?p=4040) come descritto nel precedente articolo. Se avete bisogno di più rispetto a un'espressione, si potrebbe effettuare il refactoring l'inizializzazione in un metodo statico e richiamare che.

NameOf espressioni

Un'altra aggiunta introdotta nella versione CTP3 è il supporto per le espressioni nameof. Ci sono diverse occasioni, quando è necessario utilizzare la "magia di stringhe" all'interno del codice. Tali stringhe di"magiche" sono normali C# stringhe che eseguono il mapping agli elementi di programma all'interno del codice. Ad esempio, quando lancio ArgumentNullException, utilizzare una stringa per il nome del parametro corrispondente che non era valido. Purtroppo, queste stringhe magiche non avevano alcuna convalida di tempo di compilazione ed eventuali cambiamenti di programma elemento (ad esempio rinominare il parametro) non aggiornare automaticamente la stringa magica, conseguente a un'incoerenza che mai è stata catturata dal compilatore.

In altre occasioni, come quando alzando OnPropertyChanged eventi, è possibile evitare la stringa magica via ginnastica di espressione di albero che estraggono il nome. È forse un po' più irritante data la semplicità dell'operazione, che è solo identificare il nome di elemento del programma. In entrambi i casi, la soluzione era meno ideale.

Per affrontare questa idiosincrasia, c# 6.0 consente di accedere a un nome di "elemento di programma", si tratti di un nome di classe, nome del metodo, parametro nome o attributo particolare (forse quando si utilizza la riflessione). Ad esempio, il codice in Figura 2 utilizza l'espressione nameof per estrarre il nome del parametro.

Figura 2 estrazione il nome di parametro con un'espressione Nameof

void ThrowArgumentNullExceptionUsingNameOf(string param1)
{
  throw new ArgumentNullException(nameof(param1));
}
[TestMethod]
public void NameOf_UsingNameofExpressionInArgumentNullException()
{
  try
  {
    ThrowArgumentNullExceptionUsingNameOf("data");
    Assert.Fail("This code should not be reached");
  }
  catch (ArgumentNullException exception)
  {
    Assert.AreEqual<string>("param1", exception.ParamName);
}

Come il test illustrato metodo, proprietà ParamName di ArgumentNullException ha il valore param1 — un valore impostato utilizzando l'espressione di nameof(param1) nel metodo. L'espressione nameof non è limitata ai parametri. È possibile utilizzare per recuperare qualsiasi elemento di programmazione, come mostrato Figura 3.

Nella figura 3 il recupero di altri elementi di programma

namespace CSharp6.Tests
{
  [TestClass]
  public class NameofTests
  {
    [TestMethod]
    public void Nameof_ExtractsName()
    {
      Assert.AreEqual<string>("NameofTests", nameof(NameofTests));
      Assert.AreEqual<string>("TestMethodAttribute",
        nameof(TestMethodAttribute));
      Assert.AreEqual<string>("TestMethodAttribute",
        nameof(
         Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute));
      Assert.AreEqual<string>("Nameof_ExtractsName",
        string.Format("{0}", nameof(Nameof_ExtractsName)));
      Assert.AreEqual<string>("Nameof_ExtractsName",
        string.Format("{0}", nameof(
        CSharp6.Tests.NameofTests.Nameof_ExtractsName)));
    }
  }
}

L'espressione nameof recupera solo l'identificatore finale, anche se si utilizzano nomi punteggiati più espliciti. Inoltre, nel caso di attributi, non è implicito il suffisso "Attributo". Invece, è richiesto per la compilazione. Esso offre la grande opportunità di ripulire il codice disordinato.

Primari costruttori

Gli inizializzatori di proprietà auto sono particolarmente utili in combinazione con primari costruttori. Primari costruttori darvi un modo per ridurre la cerimonia su modelli di oggetto comuni. Questa funzionalità è stata migliorata notevolmente dal mese di maggio. Gli aggiornamenti includono:

  1. Un corpo di implementazione facoltativa per il costruttore primario: Questo permette per cose come la convalida dei parametri costruttore primario e inizializzazione, che era precedentemente non supportato.
  2. Eliminazione dei parametri del campo: dichiarazione di campi tramite i parametri del costruttore primario. (Non andare avanti con questa funzionalità come l'ha definita era la decisione giusta, come esso non più forze particolari convenzioni di denominazione in modi che c# in precedenza era ambivalente).
  3. Supporto per l'espressione di corpo funzioni e proprietà (discusso più avanti in questo articolo).

Con la prevalenza di servizi Web, applicazioni multi-livello, servizi dati, Web API, JSON e tecnologie simili, una forma comune di classe è oggetto di trasferimento dati (DTO). Il DTO generalmente non ha molta comportamento di implementazione, ma si concentra sulla semplicità di archiviazione dati. Questa attenzione alla semplicità rende primari costruttori convincente. Si consideri, ad esempio, la struttura di dati coppia immutabile illustrata in questo esempio:

struct Pair<T>(T first, T second)
{
  public T First { get; } = first;
  public T Second { get; } = second;
  // Equality operator ...
}

La definizione di costruttore — coppia (stringa, stringa secondo) — è confluito nella dichiarazione di classe. Specifica i parametri del costruttore sono prima e seconda (ciascuna di tipo T). Tali parametri sono anche fatto riferimento negli inizializzatori di proprietà e assegnati alle loro proprietà corrispondenti. Quando osserviamo la semplicità di questa definizione di classe, il supporto per l'immutabilità e il costruttore necessario (inizializzatore per tutte le proprietà/campi), vedi come ti aiuta codice correttamente. Che porta a un miglioramento significativo in uno schema comune che in precedenza richiedeva verbosità inutili.

Corpi primario Costruttore specificano il comportamento del costruttore primario. Questo consente di implementare una funzionalità equivalente su primari costruttori come si può in generale sui costruttori. Ad esempio, il prossimo passo nel migliorare l'affidabilità della coppia < T > struttura di dati potrebbe essere per fornire convalida di proprietà. Tale convalida potrebbe garantire che un valore null per Pair.First sarebbe stato non valido. CTP3 include ora un corpo primario costruttore — un corpo del costruttore senza la dichiarazione, come mostrato Figura 4.

Figura 4 implementando un corpo primario Costruttore

struct Pair<T>(T first, T second)
{
  {
    if (first == null) throw new ArgumentNullException("first");
    First = first; // NOTE: Not working in CTP3
  }     
  public T First { get; }; // NOTE: Results in compile error for CTP3
  public T Second { get; } = second;
  public int CompareTo(T first, T second)
  {
    return first.CompareTo(First) + second.CompareTo(Second);
  }
// Equality operator ...
}

Per chiarezza, ho messo il corpo primario Costruttore presso il primo membro della classe. Tuttavia, questo non è un requisito di c#. Il corpo del costruttore primario può apparire in qualsiasi ordine rispetto agli altri membri della classe.

Anche se non funzionali in CTP3, un'altra caratteristica delle proprietà di sola lettura è possibile assegnare loro direttamente all'interno del costruttore (ad esempio, in primo luogo = primo). Questo non è limitato ai costruttori primarie, ma è disponibile per qualsiasi membro del costruttore.

Un'interessante conseguenza del sostegno per gli inizializzatori di proprietà auto è che elimina molti dei casi trovati nelle versioni precedenti dove era necessario dichiarazioni esplicite di campo. Il caso evidente che esso non elimina è uno scenario dove è necessaria la convalida su setter. D'altra parte, la necessità di dichiarare campi di sola lettura diventa praticamente obsoleto. Ora, ogni volta che viene dichiarato un campo di sola lettura, è possibile dichiarare una auto-proprietà di sola lettura possibilmente come private, se quel livello di incapsulamento è necessario.

Il metodo CompareTo ha parametri prima e la seconda — apparentemente sovrapposti i nomi dei parametri del costruttore primario. Perché i nomi del costruttore primario sono in ambito all'interno gli inizializzatori di proprietà auto, prima e seconda possono sembrare ambigui. Fortunatamente, questo non è il caso. Le regole di ambito perno su una dimensione diversa, che non avete visto in c# prima.

Prima c# 6.0, ambito sempre è stata identificata mediante il posizionamento di dichiarazione della variabile all'interno del codice. I parametri vengono associati all'interno del metodo aiutano a dichiarare, i campi da associare all'interno della classe e le variabili dichiarate all'interno di un if istruzione sono vincolati dal corpo condizione del se dichiarazione.

Al contrario, i parametri del costruttore primario sono vincolati da tempo. I parametri del costruttore primario sono solo "vivi", mentre il costruttore primario è in esecuzione. Questo lasso di tempo è evidente nel caso del corpo primario costruttore. Forse è meno evidente per il caso di inizializzatori di proprietà auto.

Tuttavia, come gli inizializzatori di campo tradotti in istruzioni in esecuzione come parte di una classe di inizializzazione in c# 1.0 +, inizializzatori di proprietà di auto vengono implementati nello stesso modo. In altre parole, la portata di un parametro del costruttore primario è associata alla vita l'inizializzatore di classi e l'organismo costruttore primario. Qualsiasi riferimento ai parametri del costruttore primario all'esterno di un inizializzatore di proprietà di auto o corpo primario Costruttore comporterà un errore di compilazione.

Ci sono parecchi concetti aggiuntivi relativi alla primari costruttori che sono importanti da ricordare. Solo il costruttore primario può richiamare il costruttore di base. Farlo utilizzando la parola chiave (contestuale) base seguito alla dichiarazione del costruttore primario:

class UsbConnectionException(
  string message, Exception innerException, HidDeviceInfo hidDeviceInfo):
    Exception  (message, innerException)
{
  public HidDeviceInfo HidDeviceInfo { get;  } = hidDeviceInfo;
}

Se si specificano costruttori aggiuntivi, la catena di chiamata del costruttore deve richiamare il costruttore primario ultima. Questo significa che un costruttore primario non può avere un tale inizializzatore. Tutti gli altri costruttori devono averli, supponendo che il costruttore primario non è anche il costruttore predefinito:

public class Patent(string title, string yearOfPublication)
{
  public Patent(string title, string yearOfPublication,
    IEnumerable<string> inventors)
    ...this(title, yearOfPublication)
  {
    Inventors.AddRange(inventors);
  }
}

Speriamo che questi esempi contribuiscono a dimostrare che primari costruttori portano semplicità per c#. Sono un'ulteriore opportunità per fare le cose semplici, semplicemente, invece le cose semplici in modo complesso. Occasionalmente si è garantito per le classi di avere più costruttori e chiamata catene che rendono il codice difficile da leggere. Se incontrate uno scenario dove la sintassi del costruttore primario fa codice più complesso invece di semplificarla, quindi non utilizzare primari costruttori. Per tutti i miglioramenti al c# 6.0, se non ti piace una caratteristica o se rende il codice più difficile da leggere, basta non usarlo.

Espressione di corpo funzioni e proprietà

Un'altra semplificazione della sintassi in c# 6.0 è espressione di corpo funzioni. Queste sono funzioni con nessun corpo dell'istruzione. Invece, implementarle con un'espressione che segue la dichiarazione della funzione.

Ad esempio, un override di ToString potrebbe essere aggiunto alla coppia < T > classe:

public override string ToString() => string.Format("{0}, {1}", First, Second);

Non c'è nulla di particolarmente radicale sulle funzioni di espressione corposo. Come con la maggior parte delle caratteristiche trovate in c# 6.0, essi sei destinati a fornire una sintassi semplificata per casi in cui l'implementazione è semplice. Il tipo restituito dell'espressione deve, ovviamente, corrispondere il tipo restituito, identificato nella dichiarazione della funzione. In questo caso, ToString restituisce una stringa, come fa l'espressione di implementazione della funzione. Metodi che restituiscono void o compito dovrebbe essere attuato con espressioni che non restituiscono nulla, entrambi.

La semplificazione di espressioni di corpo non è limitata alle funzioni. È inoltre possibile implementare la sola lettura (Get solo) proprietà utilizzando espressioni — espressione corposo proprietà. Ad esempio, è possibile aggiungere un membro di testo alla classe FingerPrint:

public string Text =>
  string.Format("{0}: {1} - {2} ({3})", TimeStamp, Process, Config, User);

Altre funzionalità

Ci sono parecchie caratteristiche non più previsti per c# 6.0:

  • L'operatore di proprietà indicizzata ($) non è più disponibile e non è previsto per c# 6.0.
  • La sintassi del membro di indice non sta funzionando in CTP3, anche se ci si aspetta di tornare in una versione successiva di c# 6.0:
var cppHelloWorldProgram = new Dictionary<int, string>
{
[10] = "main() {",
[20] = "    printf(\"hello, world\")",
[30] = "}"
};
  • Gli argomenti del campo in primari costruttori non fanno più parte di c# 6.0.
  • Sia il valore letterale numerico binario e il separatore numerico ('_') all'interno di un valore letterale numerico non sono attualmente certa per renderlo di rilascio alla produzione.

Ci sono un certo numero di caratteristiche non discusse qui perché essi erano già coperti nel maggio l'articolo, ma statico utilizzando le istruzioni (vedere itl.tc/?p=4038), espressioni di dichiarazione (vedere itl.tc/?p=4040) e miglioramenti di gestione delle eccezioni (vedere itl.tc/?p=4042) sono caratteristiche che sono rimaste stabili.

Conclusioni

Chiaramente, gli sviluppatori sono appassionati di c# e desidera garantire che mantiene la sua eccellenza. Il team di lingua è prendere sul serio il vostro feedback e modificando il linguaggio mentre elabora ciò che gli utenti hanno da dire. Don' t esitate a visitare roslyn.codeplex.com e lasciare che il team di sapere i vostri pensieri. Inoltre, non dimenticate di controllare itl.tc/csharp6 per aggiornamenti su c# 6.0 fino a quando non viene rilasciato.


Mark Michaelis è il fondatore di IntelliTect. Si serve anche come l'architetto capo tecnico e formatore. Dal 1996, è stato un Microsoft MVP per c#, Visual Studio Team System (VSTS) e l' Windows SDK, e fu riconosciuto come Microsoft Regional Director nel 2007. Egli serve anche su progettazione software Microsoft diversi -­revisione squadre, tra cui c#, la divisione sistemi collegati e VSTS. Michaelis presso sviluppatore conferenze, ha scritto numerosi articoli e libri e attualmente sta lavorando alla prossima edizione di "essenziale c#" (Addison-Wesley Professional).

Grazie al seguente Microsoft esperto tecnico per la revisione di questo articolo: Mads Torgersen