Domanda

Mi sembra di ricordare di leggere qualcosa su come è un male per strutture per implementare interfacce CLR via C#, ma non riesco a trovare nulla a riguardo.È cattivo?Ci sono conseguenze non intenzionali di farlo?

public interface Foo { Bar GetBar(); }
public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } }
È stato utile?

Soluzione

Ci sono diverse cose che accadono in questa domanda...

È possibile che una struttura per implementare un'interfaccia, ma ci sono preoccupazioni che si vengono a creare con la fusione, mutabilità, e le prestazioni.Vedere questo post per ulteriori dettagli: http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx

In generale, le strutture devono essere utilizzati per gli oggetti che hanno un valore di tipo semantica.Implementando un'interfaccia su una struttura che è possibile eseguire nella boxe preoccupazioni in quanto la struttura è in ghisa e indietro tra la struttura e l'interfaccia.Come risultato della boxe, le operazioni che modificano lo stato interno della struttura potrebbe non funzionare correttamente.

Altri suggerimenti

Poiché nessun altro esplicitamente previsto questa risposta vorrei aggiungere il seguente:

L'attuazione di un'interfaccia su una struttura che non ha conseguenze negative di sorta.

Qualsiasi variabile del tipo di interfaccia utilizzato per contenere una struttura che si tradurrà in un cofanetto valore della struttura utilizzata.Se la struttura è immutabile (cosa buona), quindi questo è peggio di un problema di prestazioni, a meno che:

  • utilizzando l'oggetto risultante scopo di blocco (un immenso cattiva idea in qualsiasi modo)
  • utilizza riferimento uguaglianza semantica e previsto per il lavoro per due boxed valori della stessa struttura.

Entrambi questi sarebbe improbabile, invece, si rischia di essere uno dei seguenti:

Generics

Forse molti motivi ragionevoli per le strutture di implementazione delle interfacce è in modo che possano essere utilizzati all'interno di un generico contesto con vincoli.Quando utilizzato in questo modo la variabile in questo modo:

class Foo<T> : IEquatable<Foo<T>> where T : IEquatable<T>
{
    private readonly T a;

    public bool Equals(Foo<T> other)
    {
         return this.a.Equals(other.a);
    }
}
  1. Consentire l'utilizzo della struttura come un parametro di tipo
    • così a lungo come nessun altro vincolo, come new() o class è utilizzato.
  2. Consentire l'elusione della boxe su strutture utilizzato in questo modo.

Quindi questo.NON è un interfaccia di riferimento e pertanto non causa una scatola di ciò che è messo in esso.Quando il compilatore c# consente di compilare le classi generiche e ha necessità di inserire le invocazioni dei metodi di istanza definita in base alle istanze del Tipo di parametro T è possibile utilizzare il vincolata codice operativo:

Se thisType è un tipo di valore e thisType implementa il metodo quindi ptr è passato non modificato come 'questo' puntatore a una chiamata di metodo di insegnamento, per l'attuazione del metodo da thisType.

Questo evita la boxe e dal momento che il tipo di valore che implementa l'interfaccia è deve implementare il metodo, dunque non di boxe si verificherà.Nell'esempio precedente, la Equals() invocazione che è fatto senza scatola su questo.un1.

Basso attrito Api

Più le strutture dovrebbero avere primitivo come la semantica di cui bit per bit identici valori sono considerati uguali2.Il runtime di fornitura di tali comportamenti e la implicita Equals() ma questo può essere lento.Anche questo implicito che l'uguaglianza è non esposto come un'implementazione di IEquatable<T> e, quindi, impedisce alle strutture, di essere facilmente utilizzato come chiavi per Dizionari a meno che non esplicitamente implementarla.Pertanto è comune per molti public struct tipi di dichiarare che loro attuazione IEquatable<T> (dove T è la loro auto) per rendere questo più facile e performante nonché coerente con il comportamento di molti tipi di valore entro il CLR BCL.

Tutte le primitive della BCL implementare un minimo di:

  • IComparable
  • IConvertible
  • IComparable<T>
  • IEquatable<T> (E quindi IEquatable)

Molti anche implementare IFormattable, inoltre, molti di Sistema definito i tipi di valore come DateTime, del Tempo e Guid implementare molti (o tutti) di questi.Se si implementa un simile 'ampiamente utile' tipo come un numero complesso di una struttura o di alcune larghezza fissa testuale valori quindi l'implementazione di molte di queste interfacce comuni (correttamente) renderanno il vostro struct più utili e utilizzabili.

Esclusioni

Ovviamente se l'interfaccia implica fortemente mutevolezza (come ICollection) poi realizzare è una cattiva idea in quanto significherebbe tat si sia reso la struttura mutevole (che tipo di errori già descritto in cui le modifiche si verificano sul boxed valore piuttosto che l'originale) o confondere gli utenti da ignorare le implicazioni dei metodi come Add() o la generazione di eccezioni.

Molte interfacce NON implicano mutabilità (come IFormattable e servono come il idiomatiche modo per esporre alcune funzionalità in modo coerente.Spesso l'utente della struttura, non si cura di qualsiasi boxe overhead per tale comportamento.

Riepilogo

Quando fatto in modo sensato, su immutabile tipi di valore, l'attuazione di utile interfacce è una buona idea


Note:

1:Nota che il compilatore può utilizzare questo per l'invocazione di metodi virtuali su variabili che sono noto per essere specifici struct tipo, ma in cui è necessario richiamare un metodo virtuale.Per esempio:

List<int> l = new List<int>();
foreach(var x in l)
    ;//no-op

L'enumeratore restituito dalla Lista è una struttura, un'ottimizzazione per evitare un'allocazione quando enumerazione elenco (Con alcune interessanti conseguenze).Tuttavia la semantica del foreach specificare che se l'enumeratore implementa IDisposable quindi Dispose() sarà chiamato il iterazione è completato.Ovviamente, essendo questo avviene a causa di una scatola chiamata eliminerebbe qualsiasi beneficio dell'enumeratore essere una struct (in realtà sarebbe peggio).Peggio, se smaltire chiamata modifica lo stato dell'enumeratore, in qualche modo, quindi questo sarebbe accaduto sul boxed istanza e molti bachi potrebbe essere introdotto in casi complessi.Pertanto il emesse in questo tipo di situazione è:

IL_0001:  newobj      System.Collections.Generic.List..ctor
IL_0006:  stloc.0     
IL_0007:  nop         
IL_0008:  ldloc.0     
IL_0009:  callvirt    System.Collections.Generic.List.GetEnumerator
IL_000E:  stloc.2     
IL_000F:  br.s        IL_0019
IL_0011:  ldloca.s    02 
IL_0013:  call        System.Collections.Generic.List.get_Current
IL_0018:  stloc.1     
IL_0019:  ldloca.s    02 
IL_001B:  call        System.Collections.Generic.List.MoveNext
IL_0020:  stloc.3     
IL_0021:  ldloc.3     
IL_0022:  brtrue.s    IL_0011
IL_0024:  leave.s     IL_0035
IL_0026:  ldloca.s    02 
IL_0028:  constrained. System.Collections.Generic.List.Enumerator
IL_002E:  callvirt    System.IDisposable.Dispose
IL_0033:  nop         
IL_0034:  endfinally  

Di conseguenza, l'applicazione di IDisposable non causa problemi di prestazioni e l' (deplorevole) mutevole aspetto dell'enumeratore è conservato deve il metodo Dispose in realtà fare nulla!

2:double e float sono eccezioni a questa regola, NaN valori non sono considerati uguali.

In alcuni casi può essere buono per una struttura per implementare un'interfaccia (se non è mai stato utile, sembra improbabile che i creatori di .net, avrebbe fornito per esso).Se una struttura che implementa una lettura di interfaccia solo come IEquatable<T>, memorizzazione della struttura in una posizione di memorizzazione (variabile, parametro, l'elemento di matrice, etc.) di tipo IEquatable<T> si richiede di essere in scatola (ogni struct tipo in realtà definisce due tipi di cose:una posizione di memorizzazione tipo che si comporta come un tipo di valore e un heap-tipo di oggetto che si comporta come un tipo di classe;il primo è un modo implicito per il secondo--"boxe" - e il secondo può essere convertito in un primo momento attraverso cast esplicito--"unboxing").È possibile sfruttare una struttura dell'implementazione di un'interfaccia senza boxe, tuttavia, utilizzando ciò che sono chiamati vincolata generics.

Per esempio, se uno ha un metodo CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>, tale metodo potrebbe chiamare thing1.Compare(thing2) senza scatola thing1 o thing2.Se thing1 succede, ad esempio, un Int32, il tempo di esecuzione si sa che quando si genera il codice per CompareTwoThings<Int32>(Int32 thing1, Int32 thing2).Poiché non si conosce l'esatto tipo di sia la cosa che ospita il metodo e la cosa che viene passato come parametro, non è necessario casella di loro.

Il problema più grande con le strutture che implementano le interfacce è che una struttura che viene memorizzato in una posizione del tipo di interfaccia, Object, o ValueType (a differenza di posizione dello stesso tipo) si comporterà come un oggetto di classe.Per le interfacce di sola lettura, questo non è generalmente un problema, ma per una mutazione di interfaccia come IEnumerator<T> può dare qualche strano semantica.

Si consideri, ad esempio, il codice seguente:

List<String> myList = [list containing a bunch of strings]
var enumerator1 = myList.GetEnumerator();  // Struct of type List<String>.IEnumerator
enumerator1.MoveNext(); // 1
var enumerator2 = enumerator1;
enumerator2.MoveNext(); // 2
IEnumerator<string> enumerator3 = enumerator2;
enumerator3.MoveNext(); // 3
IEnumerator<string> enumerator4 = enumerator3;
enumerator4.MoveNext(); // 4

Segnato istruzione #1 il primo enumerator1 per leggere il primo elemento.Lo stato del enumeratore sarà copiato enumerator2.Segnato istruzione #2 vi anticipo che la copia di leggere il secondo elemento, ma non riguardano enumerator1.Lo stato del secondo enumeratore poi essere copiati enumerator3, che saranno avanzate da una marcata istruzione #3.Quindi, perché enumerator3 e enumerator4 sono entrambi i tipi di riferimento, un RIFERIMENTO per enumerator3 sarà, quindi, essere copiati enumerator4, così segnato una dichiarazione di avanzare in modo efficace entrambi enumerator3 e enumerator4.

Alcune persone cercano di far finta che i tipi di valore e tipi di riferimento sono entrambi i tipi di Object, ma non è vero.Reale i tipi di valore sono convertibili in Object, ma non sono istanze di esso.Un'istanza di List<String>.Enumerator che è memorizzato in una posizione di quel tipo è un tipo di valore e si comporta come un tipo di valore;copia di un percorso di tipo IEnumerator<String> verrà convertito in un tipo di riferimento, e si comporterà come un tipo di riferimento.Quest'ultimo è una sorta di Object, ma il primo non lo è.

BTW, un paio di note:(1) In generale, mutevole tipi di classe dovrebbero avere la loro Equals metodi di prova di riferimento per l'uguaglianza, ma non c'è modo decente per una confezione di struttura per farlo;(2) nonostante il suo nome, ValueType è un tipo di classe, non un tipo di valore;tutti i tipi derivati da System.Enum sono i tipi di valore, come lo sono di tutti i tipi, che derivano dall' ValueType con l'eccezione di System.Enum, ma sia ValueType e System.Enum sono i tipi di classe.

Le strutture sono implementati come valore i tipi e le classi sono i tipi di riferimento.Se si dispone di una variabile di tipo Pippo, e si memorizza un'istanza di Fubar in esso, si "Box" in un tipo di riferimento, così sconfiggere il vantaggio dell'utilizzo di una struttura, in primo luogo.

L'unico motivo che vedo per usare una struct invece di una classe è, perchè è un tipo di valore e non un tipo di riferimento, ma la struttura non può ereditare da una classe.Se si dispone di struttura ereditare un'interfaccia, e si passa intorno interfacce, si perde il valore digitare la natura della struttura.Potrebbe anche solo fare una classe se avete bisogno di interfacce.

(Beh, niente di importante da aggiungere, ma non hanno la modifica prodezza di sicurezza così qui va..)
Perfettamente Sicuro.Niente di illegale, con l'implementazione di interfacce su strutture.Tuttavia, si dovrebbe mettere in discussione perché ci si vuole fare.

Tuttavia ottenere un riferimento all'interfaccia di una struttura verrà BOX si.Così le prestazioni e così via.

L'unico scenario valido che posso pensare in questo momento è illustrato nel mio post qui.Quando si desidera modificare una struct è stato memorizzato in una raccolta, è necessario fare un'ulteriore interfaccia esposta la struttura.

Penso che il problema è che causa la boxe perché le strutture sono tipi di valore, quindi c'è un leggero calo delle prestazioni.

Questo link suggerisce che ci potrebbero essere altri problemi con esso...

http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx

Non ci sono conseguenze di una struct implementazione di un'interfaccia.Per esempio il built-in sistema di strutture di implementare interfacce come IComparable e IFormattable.

C'è molto poco motivo per un tipo di valore per implementare un'interfaccia.Dal momento che non è possibile sottoclasse di un tipo di valore, si può sempre fare riferimento ad esso come il suo tipo concreto.

A meno che, naturalmente, si dispone di più le strutture tutte implementare la stessa interfaccia, potrebbe essere marginalmente utile, ma a quel punto mi consiglia di utilizzare una classe e di farlo bene.

Naturalmente, l'attuazione di un'interfaccia, si pugilato della struttura, in modo che ora si trova nel mucchio, e non sarà in grado di passare dal valore di più...davvero Questo rafforza la mia opinione che si debba usare una classe in questa situazione.

Le strutture sono esattamente come le classi che vivono nello stack.Non vedo il motivo perchè dovrebbero essere "a rischio".

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top