Giugno 2018

Volume 33 Numero 6

Il presente articolo è stato tradotto automaticamente.

.NET framework - problemi di tupla: Perché le tuple c# visualizzato interrompere le linee guida

Dal feed di Mark Michaelis | 2018 giugno

Torna nel numero di agosto 2017 di MSDN Magazine ho scritto un articolo dettagliato in c# 7.0 e il relativo supporto per le tuple (msdn.com/magazine/mt493248). Al momento è possibile glossed tramite il fatto che il tipo di tupla introdotte con interruzioni di c# 7.0 (internamente di tipo ValueTuple <>...) diverse linee guida di un tipo di valore ben strutturato, vale a dire:

• Non dichiarare campi pubblici sono protetti (invece di incapsulare con una proprietà).

• Non definiscono i tipi di valore modificabile.

• Non creare tipi di valore maggiore di 16 byte di dimensioni.

Queste linee guida sono state implementate dalla versione 1.0 di c# e ancora qui in c# 7.0, è state generate per vento per definire il tipo di dati System.ValueTuple <>.... Tecnicamente, System.ValueTuple <>... è una famiglia di tipi di dati con lo stesso nome ma di grado variabile (in particolare, il numero di parametri di tipo). Che cos'è così speciale su questo particolare tipo di dati che non vengono più applicano queste linee guida rispettato prolungata? E come è possibile la comprensione delle circostanze in cui si applicano alle linee guida, o non si applicano, ovvero contribuire a ottimizzare le applicazioni affinché la definizione dei tipi di valore?

Iniziamo la discussione con l'obiettivo di incapsulamento e i vantaggi delle proprietà e campi. Si consideri, ad esempio, un tipo di valore arco che rappresenta una parte la circonferenza del cerchio. E viene definito per il raggio del cerchio, l'angolo iniziale (in gradi) del primo punto nell'arco e l'angolo di apertura (in gradi) dell'ultimo punto l'arco, come illustrato nella figura 1.

Figura 1 che definisce un arco

public struct Arc
{
  public Arc (double radius, double startAngle, double sweepAngle)
  {
    Radius = radius;
    StartAngle = startAngle;
    SweepAngle = sweepAngle;
  }

  public double Radius;
  public double StartAngle;
  public double SweepAngle;

  public double Length
  {
    get
    {
      return Math.Abs(StartAngle - SweepAngle)
        / 360 * 2 * Math.PI * Radius;
    }
  }

  public void Rotate(double degrees)
  {
    StartAngle += degrees;
    SweepAngle += degrees;
  }

  // Override object.Equals
  public override bool Equals(object obj)
  {
    return (obj is Arc)
      && Equals((Arc)obj);
  }

        // Implemented IEquitable<T>
  public bool Equals(Arc arc)
  {
    return (Radius, StartAngle, SweepAngle).Equals(
      (arc.Radius, arc.StartAngle, arc.SweepAngle));
  }

  // Override object.GetHashCode
  public override int GetHashCode() =>
    return (Radius, StartAngle, SweepAngle).GetHashCode();

  public static bool operator ==(Arc lhs, Arc rhs) =>
    lhs.Equals(rhs);

  public static bool operator !=(Arc lhs, Arc rhs) =>
    !lhs.Equals(rhs);
}

Non dichiarare campi pubblici sono protetti

In questa dichiarazione, arco è un tipo di valore, definito mediante la parola chiave struct, con tre campi pubblici che definiscono le caratteristiche dell'arco. Sì, potrebbero aver usate le proprietà, ma si è scelto di utilizzare i campi pubblici in questo esempio in modo specifico perché viola delle linee guida per il primo: non dichiarare campi pubblici sono protetti.

Sfruttando i campi pubblici anziché le proprietà, la definizione di arco non dispone di più elementare di principi di progettazione orientata agli oggetti, ovvero l'incapsulamento. Ad esempio, cosa accade se ho deciso di modificare la struttura di dati interne per usare il raggio, angolo e arco lunghezza, iniziale, ad esempio, anziché l'angolo di apertura? In questo modo interromperebbe ovviamente l'interfaccia per arco e tutti i client sarebbe necessario modificare il codice.

Analogamente, con le definizioni di Radius, StartAngle e SweepAngle, non si dispone di alcuna convalida. RADIUS, ad esempio, è stato possibile assegnare un valore negativo. E i valori negativi per StartAngle e SweepAngle potrebbero essere consentiti, non sarebbe un valore maggiore di 360 gradi. Purtroppo, poiché arco è definito utilizzando i campi pubblici, non esiste alcuna possibilità di aggiungere la convalida per proteggersi da tali valori. Sì, si potrebbe aggiungere la convalida nella versione 2 modificando i campi di proprietà, ma potrebbe compromettere la compatibilità di versione della struttura dell'arco. Codice che ha richiamato i campi esistenti compilato causa l'interruzione in fase di esecuzione, come sarebbe qualsiasi codice (anche se ricompilate) che passa il campo come dal parametro ref.

Data la linea guida che i campi non devono essere pubblico o protetto, vale la pena notare che le proprietà, in particolare con i valori predefiniti, è diventata più semplice definire più campi espliciti incapsulati dalle proprietà, grazie al supporto c# 6.0 per inizializzatori di proprietà. Ad esempio, questo codice:

public double SweepAngle { get; set; } = 180;

è più semplice rispetto a questo:

private double _SweepAngle = 180;

public double SweepAngle {
  get { return _SweepAngle; }
  set { _SweepAngle = value; }
}

Il supporto di inizializzatore di proprietà è importante perché, in caso contrario, una proprietà implementata automaticamente che richiede l'inizializzazione sarebbe necessario un costruttore di accompagnamento. Di conseguenza, la linea guida: Rende (campi privati anche) "Prendere in considerazione le proprietà implementate automaticamente campi" rilevamento, sia perché il codice è più conciso e perché non è più possibile modificare i campi da all'esterno di loro proprietà che lo contiene. Tutte queste Ottimizza per un'altra regola, "Evitare l'accesso ai campi all'esterno le relative proprietà che lo contiene," che mette in evidenza il principio di incapsulamento precedenti descritto dati anche da altri membri della classe.

A questo punto consente di restituire per il tipo di tupla c# 7.0 ValueTuple <>.... Nonostante la linea guida sui campi esposti, ValueTuple < T1, T2 >, ad esempio, viene definito come segue:

public struct ValueTuple<T1, T2>
  : IComparable<ValueTuple<T1, T2>>, ...
{
  public T1 Item1;
  public T2 Item2;
  // ...
}

Che cosa rende speciali ValueTuple <>...? A differenza della maggior parte delle strutture di dati, la tupla c# 7.0, ormai detta tupla, non è stato sull'intero oggetto (ad esempio un oggetto persona o CardDeck). Invece, era sulle singole parti raggruppate in modo arbitrario per scopi di trasporto, per cui potrebbe essere restituiti da un metodo senza preoccuparsi di mediante out o ref. Mads Torgersen utilizza l'analogia di un gruppo di persone che risultano essere nello stesso bus, ovvero in cui il bus è simile a una tupla e le persone sono, ad esempio gli elementi nella tupla. Gli elementi sono raggruppati in un parametro di tuple restituito perché sono tutti destinati a restituire al chiamante, non perché hanno necessariamente le altre associazioni tra loro. In effetti, è probabile che il chiamante verrà quindi recuperare i valori dalla tupla e utilizzarle singolarmente anziché come un'unità.

L'importanza di singoli elementi anziché dell'intero rende il concetto di incapsulamento meno interessanti. Dato che gli elementi in una tupla possono essere completamente non correlati tra loro, è spesso necessario racchiuderle in modo che la modifica di Item1, ad esempio, potrebbe sull'Item2. (Al contrario, modificare la lunghezza di arco richiederebbe una modifica a uno o entrambi gli aspetti in modo incapsulamento è necessaria.) Inoltre, non sono presenti valori non validi per gli elementi archiviati all'interno di una tupla. Verrà applicata alcuna operazione di convalida nel tipo di dati dell'elemento stesso, non nell'assegnazione di una delle proprietà degli elementi nella tupla.

Per questo motivo, le proprietà nella tupla non forniscono alcun valore, e nessun valore futuro qualsiasi che Impossibile forniscono. In breve, se si intende definire un tipo i cui dati sono modificabili senza che sia necessario per la convalida, è possibile utilizzare anche i campi. Si potrebbe voler usare proprietà può avere accessibilità diversi tra i metodi get e set. Tuttavia, presupponendo che modificabilità è accettabile, sta per sfruttare i vantaggi delle proprietà con accessibilità getter/setter diversi, ovvero. Questa operazione genera un'altra domanda, ovvero il tipo di tupla deve essere modificabile?

Non si definisce i tipi di valore modificabile

La linea guida successiva da prendere in considerazione è che il tipo di valore modificabile. Ancora una volta, nell'esempio arco (illustrato nel codice in figura 2) viola la linea guida. È evidente se si pensa, ovvero un tipo di valore passa una copia, pertanto la modifica la copia non saranno visibili da parte del chiamante. Tuttavia, se il codice figura 2 viene illustrato il concetto di solo modificando tale copia, la leggibilità del codice non lo è. Da una prospettiva di migliorare la leggibilità, potrebbe sembrare le modifiche dell'arco.

Figura 2 tipi di valore vengono copiati in modo che il chiamante non osserva la modifica

[TestMethod]
public void PassByValue_Modify_ChangeIsLost()
{
  void Modify(Arc paramameter) { paramameter.Radius++; }
  Arc arc = new Arc(42, 0, 90);
  Modify(arc);
  Assert.AreEqual<double>(42, arc.Radius);
}

Che cos'è poco chiaro, che sono in ordine per uno sviluppatore prevedere il comportamento di copia, dovranno essere certi che arco sia stato un tipo di valore. Tuttavia, non contiene alcun ovvio dal codice sorgente che indica il comportamento di tipo valore (ma in realtà, l'IDE di Visual Studio verrà mostrato un tipo di valore come uno struct se si passa il mouse sul tipo di dati). È eventualmente possibile sostenere che i programmatori c# devono sapere valore digitare rispetto a una semantica dei tipi riferimento, in modo che il comportamento figura 2 è previsto. Tuttavia, si consideri lo scenario in figura 3quando il comportamento di copia non è ovvio.

Figura 3 tipi di valore modificabile si comportano in modo imprevisto

public class PieShape
{
  public Point Center { get; }
  public Arc Arc { get; }

  public PieShape(Arc arc, Point center = default)
  {
    Arc = arc;
    Center = center;
  }
}

public class PieShapeTests
{
  [TestMethod]
  public void Rotate_GivenArcOnPie_Fails()
  {
    PieShape pie = new PieShape(new Arc(42, 0, 90));
    Assert.AreEqual<double>(90, pie.Arc.SweepAngle);
    pie.Arc.Rotate(42);
    Assert.AreEqual<double>(90, pie.Arc.SweepAngle);
  }
}

Si noti che, nonostante funzione rotazione dell'arco di chiamata, l'arco, infatti, mai ruota. Perché? Questo comportamento confusione è dovuto alla combinazione dei due fattori. In primo luogo, arco è un tipo di valore che ne determina il passaggio per valore anziché per riferimento. Di conseguenza, la chiamata a torta. Arco restituisce una copia dell'arco, anziché restituire la stessa istanza dell'arco è stata creata un'istanza nel costruttore. Ciò non sarebbe costituire un problema, se non è stato per il secondo fattore. La chiamata di ruota dovrà modificare l'istanza di arco archiviato all'interno di grafici a torta, ma in realtà, modifica la copia restituita dalla proprietà dell'arco. E questo motivo sono disponibili le linee guida, "non definisce i tipi di valore modificabile."

Come prima, le tuple in c# 7.0 ignorare questa linea guida ed espone i campi pubblici che, per definizione, rendere modificabile ValueTuple <>.... Nonostante questa violazione, ValueTuple <>... non presentano svantaggi stesso come arco. Il motivo è che l'unico modo per modificare la tupla è tramite il campo di elemento. Tuttavia, il compilatore c# non consente la modifica di un campo (o proprietà) restituito da un tipo di contenitore (se il tipo contenitore è un tipo riferimento, tipo di valore o persino una matrice o altro tipo di raccolta). Ad esempio, non verrà compilato il codice seguente:

pie.Arc.Radius = 0;

Né verrà questo codice:

pie.Arc.Radius++;

Queste istruzioni avranno esito negativo con il messaggio "errore CS1612: Non è possibile modificare il valore restituito di 'PieShape.Arc' perché non è una variabile." In altre parole, la linea guida non è necessariamente precisa. Anziché evitando tutti i tipi di valore modificabile, la chiave è evitare la mutazione di funzioni (proprietà di lettura/scrittura sono consentite). Che conoscenze, naturalmente, presuppone che la semantica del valore racchiusa figura 2 sono sufficientemente evidente in modo che il comportamento di tipo di valore intrinseco è previsto.

Non creare tipi di valore superiore a 16 byte

Questa linea guida è necessario a causa di quanto spesso viene copiato il tipo di valore. In realtà, fatta eccezione per un parametro ref o out parametro, i tipi di valore vengono copiati virtualmente ogni volta che vi si accede. Ciò è vero se l'assegnazione di un'istanza del tipo di valore a un altro (ad esempio arco = arco in figura 3) o una chiamata al metodo (ad esempio Modify(arc) illustrato nel figura 2). Per motivi di prestazioni, la linea guida riguarda per mantenere ridotte le dimensioni di tipo valore.

In realtà è che le dimensioni di un oggetto possono ValueTuple <>... spesso superiore a 128 bit (16 byte) perché un ValueTuple <>... può contenere singoli sette elementi (e persino superiore se si specifica un altro tupla per il parametro di tipo ottavo). Motivo per cui, quindi, è la tupla di c# 7.0 definita come un tipo di valore?

Come accennato in precedenza, la tupla è stato introdotto come una funzionalità del linguaggio per consentire più valori restituiti senza la sintassi complessi per out o ref parametri. Il modello generale, è stato quindi, per costruire e restituire una tupla e quindi analizzare la struttura creane si torna al chiamante. In realtà, passando una tupla verso il basso dello stack mediante un parametro restituito è simile al passaggio di un gruppo di argomenti dello stack per una chiamata al metodo. In altre parole, le tuple restituite sono simmetriche con elenchi di parametri singoli per quanto riguarda memoria copie sono.

Se è stato dichiarato come tipo di riferimento la tupla, sarebbe necessario costruire il tipo nell'heap e inizializzarlo con i valori degli elementi, ovvero potenzialmente la copia di un valore o un riferimento all'heap. In entrambi i casi, un'operazione di copia della memoria è obbligatorio, in modo analogo a quello della copia in memoria di un tipo di valore. Inoltre, a un certo punto più avanti nel tempo quando la tupla di riferimento non è più accessibile, il garbage collector sarà necessario ripristinare la memoria. In altre parole, una tupla di riferimento richiede la copia della memoria, nonché pressione aggiuntiva dal garbage collector, effettua una tupla di tipo di valore dell'opzione più efficiente. (Nei rari casi che una tupla di valore non è più efficiente, è possibile comunque ricorrere alla versione del tipo di riferimento, tupla <>....)

Mentre ortogonali completamente l'argomento principale dell'articolo, si noti l'implementazione di Equals e GetHashCode in figura 1. È possibile vedere come tuple forniscono un collegamento per l'implementazione di Equals e GetHashCode. Per altre informazioni, vedere "Utilizzo di tuple per verificarne l'uguaglianza di sostituzione e GetHashCode".

Conclusioni

A prima vista può apparire sorprendente per tuple che devono essere definiti come tipi di valore non modificabile. Dopo tutto, il numero di tipi di valore non modificabile disponibili in .NET Core e .NET Framework è minimo e sono presenti le linee guida che chiamano per i tipi di valore come non modificabile e incapsulato con le proprietà di programmazione da molto tempo. È inoltre disponibile l'influenza della caratteristica approccio non modificabili, per impostazione predefinita a F #, che lavorano in c# language finestre di progettazione per fornire una sintassi abbreviata per dichiarare le variabili non modificabile o definire i tipi non modificabili. (Anche se non tali a sintassi abbreviata è attualmente presi in considerazione per c# 8.0, le strutture di sola lettura sono stati aggiunti C# 7.2 come mezzo per verificare uno struct non modificabile.)

Tuttavia, quando approfondire i dettagli, vengono visualizzati numerosi fattori importanti. tra cui:

I tipi di riferimento • impongano un impatto aggiuntivo sulle prestazioni con operazioni di garbage collection.

• Le tuple sono in genere temporanee.

• Tupla elementi non sono prevedibili necessari per l'incapsulamento con le proprietà.

• Le tuple anche che sono di grandi dimensioni (dalle linee guida di tipo valore) non dispone di operazioni di copia notevole quantità di memoria rispetto a quello di un'implementazione del riferimento tupla.

In sintesi, esistono molti fattori che prediligono una tupla di tipo valore con i campi pubblici nonostante le linee guida standard. Alla fine, linee guida sono semplicemente tale, linee guida. Non possano ignorare, ma non fornita sufficienti e consiglio, documentate in modo esplicito, ovvero causa, è anche possibile colori all'esterno di righe in alcuni casi.

Per ulteriori informazioni sulle linee guida per la definizione dei tipi di valore e si esegue l'override di Equals e GetHashCode, estrarre capitoli 9 e 10 nel mio libro essenziali c#: "Essenziali c# 7.0" (IntelliTect.com/EssentialCSharp), che deve essere nel mese di maggio.


Mark Michaelis è fondatore di IntelliTect, in cui ha serve come responsabile dell'architettura tecnica e trainer. Per quasi due decenni egli è stato MVP di Microsoft ed è un direttore regionale Microsoft dal 2007. Serve Michaelis nella progettazione del software Microsoft diversi esaminare i team, inclusi c#, Mi crosoft Azure, SharePoint e Visual Studio ALM. Egli pronuncia conferenze developer e ha scritto numerosi libri, tra cui la più recente, "Es-sential c# 6.0 (Edition 5)" (itl.tc/EssentialCSharp). È possibile contattarlo su Facebook al facebook.com/Mark.Michaelis, nel suo blog all'indirizzo IntelliTect.com/Mark, su Twitter: @markmichaelis o tramite posta elettronica al Mark@IntelliTect.com.