2013-05-30 9 views
8

Vorrei creare un tipo in C# con valore come semantica. È immutabile e ha un basso ingombro di memoria. Tuttavia, è per lo più accessibile tramite un'interfaccia implementata. In questo caso, un valore dovrebbe essere inserito in una scatola, il che significa che il valore effettivo dovrebbe essere copiato dallo stack all'heap. Quindi mi chiedo se c'è qualche vantaggio nell'usare un tipo di valore (struct) invece di un tipo di riferimento (classe)?Valore vs tipi di riferimento quando si usano le interfacce in C#

Per illustrare la mia situazione, fornisco il seguente esempio di un'interfaccia I con le implementazioni ReferenceTypeImplementation e ValueTypeImplementation:

interface I 
{ 
    int GetSomeInt(); 
} 

class ReferenceTypeImplementation : I 
{ 
    public readonly int i; 

    public ReferenceTypeImplementation (int i) 
    { 
     this.i = i; 
    } 

    public int GetSomeInt() 
    { 
     return i*2; 
    } 
} 

struct ValueTypeImplementation : I 
{ 
    public readonly int i; 

    public ValueTypeImplementation (int i) 
    { 
     this.i = i; 
    } 

    public int GetSomeInt() 
    { 
     return i*2; 
    } 
} 

userei quasi esclusivamente questo tipo utilizzando l'interfaccia I come

I i1 = new ReferenceTypeImplementation(1); 
I i2 = new ValueTypeImplementation(1); 

C'è qualche vantaggio nell'uso di ValueTypeImplementation su ReferenceTypeImplementation?

+0

Come nota, fare attenzione a credere/perpetuare le semplificazioni strette dei tipi di valore e dello stack. http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx –

+0

Personalmente non userei affatto un'interfaccia per un tipo immutabile con valore semantica. Per i principianti, è impossibile garantire al codice * client * che i valori nel tipo "immutabile" non cambino. (Se hai bisogno di modi speciali per creare istanze del tipo, puoi sempre usare ovviamente le classi di fabbrica.) Ma trovo che le interfacce per tipi immutabili semplici con semantica del valore non valgano a causa degli inconvenienti. –

+0

possibile duplicato di [Structs, Interfaces and Boxing] (http: // stackoverflow.it/questions/3032750/structs-interfaces-and-boxing) –

risposta

10

C'è qualche vantaggio nell'utilizzo di ValueTypeImplementation su ReferenceTypeImplementation?

Non se lo si utilizzerà tramite l'interfaccia. Come hai detto, questo inserirà il tipo di valore, il che annulla qualsiasi potenziale miglioramento delle prestazioni.

Inoltre, il fatto che il vostro previsto utilizzo principale sarà tramite l'interfaccia suggerirebbe che vi aspettate la semantica di riferimento, che ripropone che un class sarebbe più appropriato in questo caso.

2

Convertire una struttura in un'interfaccia causa la boxe. Chiamare un implicito implementato membro su una struct non causa la boxe:

interface I { void Foo(); } 
struct S : I { public void Foo() {} } 

S s = new S(); 
s.Foo(); // No boxing. 

I i = s; // Box occurs when casting to interface. 
i.Foo(); 

Qui nel tuo caso, se si può lavorare con implicita implementazione/chiamare allora siete meglio con struct dal vostro sono evitando;

(a) boxe

(b) senza overhead di memoria supplementare (che è applicabile su qualsiasi tipo di allocazione di riferimento).

0

Se tipo Foo implementa IBar, ci sono due modi con cui i membri della IBar possono essere utilizzati in un'istanza di Foo:

  • Un riferimento all'istanza (per tipi di classe) oppure un riferimento un oggetto heap contenente una copia dell'istanza (per i tipi di valore) può essere archiviato in una variabile di tipo IBar

  • L'istanza (per i tipi di valore) o un riferimento ad essa (per i tipi di classe) possono essere memorizzati in una variabile (o campo, matrice e lement o altra posizione di archiviazione) di un tipo generico vincolato a IBar.

Una posizione di un tipo di classe o interfaccia tipo stoccaggio terrà sia un riferimento a un oggetto mucchio, o null. Una posizione di memorizzazione di un tipo di valore primitivo contiene una raccolta di bit che rappresentano il suo valore. Una posizione di memorizzazione di un tipo di valore non primitivo contiene i contenuti concatenati di tutti i suoi campi di istanza pubblici e privati. Una posizione di archiviazione di tipo generico T contiene un riferimento di heap se T è un tipo di classe o un tipo di interfaccia, una raccolta di bit che rappresenta un valore primitivo se T è un tipo di valore primitivo o i valori concatenati di un campo se T è un tipo di struttura; questa determinazione si basa sul tipo effettivo di T e non su nessuno dei suoi vincoli.

Tornando ai tipi Foo e IBar, memorizzando un Foo in IBar farà sì che il sistema per creare un nuovo oggetto mucchio che conterrà il contenuto concatenato di tutti i campi pubblici e privati ​​di Foo, e memorizzare un riferimento a quel mucchio oggetto in IBar. Questo oggetto heap si comporterà come come qualsiasi altro oggetto heap, anziché come un percorso di memorizzazione di tipo valore. Avere cose che a volte si comportano come tipi di valore e, a volte, come tipi di classi possono creare confusione; è meglio evitare una semantica mista quando possibile.

Il tempo principale può essere utile per una struttura che implementa un'interfaccia nei casi in cui l'utilizzo seguirà il secondo schema sopra riportato. Ad esempio, si potrebbe avere un metodo:

// Return 1, 2, or 3 depending upon how many matching or different things there are 
int CountUniqueThings<T>(T first, T second, T third) where T:IEquatable<T> 

In tale situazione, se si dovesse chiamare CountUniqueThings(4, 6, 6), il sistema potrebbe invocare il metodo IEquatable<System.Int32>.Equals(System.Int32) direttamente sui parametri passati-in di tipo System.Int32. Se il metodo era stato invece dichiarato:

int CountUniqueThings(IEquatable<System.Int32> first, 
         IEquatable<System.Int32> second, 
         IEquatable<System.Int32> third) 

poi il chiamante avrebbe dovuto creare un nuovo oggetto mucchio per mantenere il valore 4 Int32, un secondo per mantenere il valore 6, una terza che potrebbe anche contenere il valore 6 e dovrebbe quindi passare i riferimenti a tali oggetti al metodo CountUniqueThings. Icky.

La creazione di oggetti heap ha un costo certo. Se si crea qualcosa di un tipo di valore si evita la necessità di creare oggetti heap per contenere la maggior parte delle istanze, questa può essere una grande vittoria. Se ogni istanza creata creerebbe la creazione di un oggetto heap per contenere una copia, tuttavia (ad esempio per l'assegnazione a una variabile di tipo interfaccia), il vantaggio di tipo valore si annullerebbe completamente. Se le istanze richiederebbero in media la creazione di più di un oggetto heap per conservare le copie (ogni volta che un tipo viene convertito dal tipo di heap al tipo di valore e viceversa, sarà richiesta una nuova istanza), l'utilizzo di tipi di valore potrebbe essere molto inferiore efficiente rispetto alla semplice costruzione di un'istanza di tipo classe a cui il codice potrebbe passare attorno a un riferimento.

Problemi correlati