2015-07-24 11 views
6

Supponiamo che io sono un tipo struct attuazione IDisposible, e se uso i codici di seguito:?è la copia profonda di tipo struct anche disposto quando nel blocco di “Uso ......”

using (MyStruct ms = new MyStruct()) 
{ 
    InnerAction(ms); //Notice "InnerAction" is "InnerAction(MyStruct ms)" 
} 

Naturalmente vedere dopo il blocco di utilizzo, il ms è disposto. Per quanto riguarda la struttura in "InnerAction"? È ancora vivo a causa di una copia profonda o è anche disposto?

Se è ancora attivo (non disposto), Devo usare "ref" per "InnerAction"?

Vi prego di dare la prova :)

Thx tutti.

+1

Non c'è alcuna "copia profonda" e quindi non è rimasto nulla da smaltire. – Henrik

+11

I tipi di struttura 'IDisposable' sono [una cattiva idea] (http://ericlippert.com/2011/03/14/to-box-or-not-to-box/) ... – xanatos

+1

Le strutture mutevoli sono [un brutto idea] (http://stackoverflow.com/questions/441309/why-are-mutable-struct-evil) Per implementare i suggerimenti usa e getta deve avere uno stato, quindi deve essere modificabile. – weston

risposta

4

È peggio di quanto si pensi: ms non è nemmeno smaltito.

Il motivo è che l'istruzione using crea una copia interna che chiama dispose in un costrutto try/finally.

Considerate questo LinqPad example:

void Main() 
{ 
    MyStruct ms; 
    using (ms = new MyStruct()) 
    { 
     InnerAction(ms); 
    } 

    ms.IsDisposed.Dump(); 
    _naughtyCachedStruct.IsDisposed.Dump(); 
} 

MyStruct _naughtyCachedStruct; 

void InnerAction(MyStruct s) 
{ 
    _naughtyCachedStruct = s; 
} 

struct MyStruct : IDisposable 
{ 
    public Boolean IsDisposed { get; set; } 

    public void Dispose() 
    { 
     IsDisposed = true; 
    } 
} 

Ecco alcuni dei IL decompilato:

IL_0000: nop   
IL_0001: ldloca.s 01 // CS$0$0000 
IL_0003: initobj  UserQuery.MyStruct 
IL_0009: ldloc.1  // CS$0$0000 
IL_000A: dup   
IL_000B: stloc.0  // ms 
IL_000C: dup   
IL_000D: stloc.0  // ms 
IL_000E: stloc.2  // CS$3$0001 
IL_000F: nop   
IL_0010: ldarg.0  
IL_0011: ldloc.0  // ms 

Si noti che in IL_000E un compilatore generato locale (CS$3$0001) viene creato e una copia del ms viene memorizzato anche lì . Più tardi ...

IL_001B: ldloca.s 02 // CS$3$0001 
IL_001D: constrained. UserQuery.MyStruct 
IL_0023: callvirt System.IDisposable.Dispose 
IL_0028: nop   
IL_0029: endfinally 

Dispose è chiamato contro questo locale, non ms (che è memorizzato nella posizione 0).

Il risultato è che sia lo ms sia la copia su cui si trova lo InnerAction non sono stati eliminati.

Conclusione: non utilizzare le strutture nelle istruzioni using.

MODIFICA: come @Weston fa notare nei commenti, you can manually box the struct and act on the boxed instance, poiché sopravvive sull'heap. In questo modo è possibile ottenere l'istanza da smaltire, ma se è stata restituita alla struct nell'istruzione using, si finirà per archiviare una copia prima che l'istanza sia stata eliminata. Inoltre, il pugilato rimuove il vantaggio di rimanere fuori dal mucchio, che presumibilmente fino a qui.

MyStruct ms = new MyStruct(); 
var disposable = (IDisposable)ms; 
using (disposable) 
{ 
    InnerAction(disposable); 
} 

((MyStruct)disposable).IsDisposed.Dump(); 
+0

Questo è quello che mi è venuto in mente, ma poi ho visto questo: http://stackoverflow.com/a/1330596/360211 Dice "i tipi di valore non sono inseriti nel box" ... – weston

+0

Non sono inseriti in una scatola che "usare" si riferisce a un oggetto sull'heap. L'IL sopra mostra questo. – codekaizen

+0

Oh sì, immagino che la boxe possa salvare la situazione come * dovrebbe * fare riferimento alla stessa istanza. – weston

2

Il comportamento del codice dipende dall'implementazione interna di MyStruct.

consideri la seguente implementazione:

struct MyStruct : IDisposable 
{ 
    private A m_A = new A(); 
    private B m_B = new B(); 

    public void Dispose() 
    { 
     m_A.Dispose(); 
     m_B.Dispose(); 
    } 
} 

class A : IDisposable 
{ 
    private bool m_IsDisposed; 
    public void Dispose() 
    { 
     if (m_IsDisposed) 
      throw new ObjectDisposedException(); 
     m_IsDisposed = true; 
    } 
} 

class B : IDisposable 
{ 
    private bool m_IsDisposed; 
    public void Dispose() 
    { 
     if (m_IsDisposed) 
      throw new ObjectDisposedException(); 
     m_IsDisposed = true; 
    } 
} 

Nel codice precedente, l'attuazione MyStruct solo delega la chiamata Dispose per altri tipi di riferimento. In tal caso, l'istanza nel tuo esempio può essere considerata come "Disposable" dopo che il blocco using è finito. Un comportamento simile può essere ottenuto salvando un riferimento interno a un membro booleano che indica se la classe è stata eliminata.

Tuttavia negli esempi nella risposta di @ codekaizen e nel commento di @ xanatos, il comportamento è che solo una copia viene eliminata, come indicato qui.

La linea di fondo è che si ha la possibilità di fare in modo che la struct si comporti correttamente con il pattern Disposed, ma eviterei questo perché è molto incline al bug.

+1

Questa è una buona intuizione, e in sostanza il modello utilizzato da 'System.Threading.CancellationToken' dove viene utilizzato per fornire un accesso sicuro allo stato condiviso. – codekaizen

0

penso che sia un peccato che gli esecutori di C# deciso che impiegano using con una struttura dovrebbe causare tutti i metodi su tale struttura (tra cui Dispose) per ricevere copie di esso, dal momento che tale comportamento porta a codice più lento di quanto sarebbe operano sul originale , preclude quella che altrimenti sarebbe una semantica utile, e in nessuna circostanza posso identificare piombo a ciò che altrimenti sarebbe codice rotto funzionando correttamente. Tuttavia, il comportamento è quello che è.

Di conseguenza, suggerirei che nessuna struttura dovrebbe implementare IDisposable in qualsiasi modo che è previsto che modifichi la struttura stessa. Gli unici tipi di struttura che implementano IDisposable devono montare uno o entrambi i seguenti motivi:

  1. La struttura serve per incapsulare un riferimento immutabile di un oggetto, e la struttura si comporta come se lo stato di quell'oggetto come propria. Non riesco a pensare a dove ho visto questo modello usato per incapsulare oggetti che richiedono lo smaltimento, ma sembrerebbe possibile.

  2. Il tipo di struttura implementa un'interfaccia che eredita IDisposable e alcune delle cui implementazioni richiedono la pulitura. Se la struttura non richiede di per sé la pulizia e il suo metodo di smaltimento non fa nulla, il fatto che il metodo di smaltimento sia invocato su una copia non avrebbe alcuna conseguenza al di là del fatto che il sistema perderà tempo a fare una copia inutile della struttura prima di invocare un do -Niente metodo su di esso.

Si noti che il comportamento di using dichiarazione C# s 'causa problemi non solo quando si tratta di Dispose, ma anche quando si tratta di l'invocazione di altri metodi. Considerare:

void showListContents1(List<string> l) 
{ 
    var en = l.GetEnumerator(); 
    try 
    { 
    while(en.MoveNext()) 
     Console.WriteLine("{0}", en.Current); 
    } 
    finally 
    { 
    en.Dispose(); 
    } 
} 

void showListContents(List<string> l) 
{ 
    using(var en = l.GetEnumerator()) 
    { 
    while(en.MoveNext()) 
     Console.WriteLine("{0}", en.Current); 
    } 
} 

Mentre i due metodi laici cercare equivalente, la prima funzionerà e la seconda non. Nel primo metodo, ogni chiamata a MoveNext agirà in base alla variabile en e quindi farà avanzare l'enumeratore. Nel secondo, ogni chiamata a MoveNext agirà su una copia diversa di en; nessuno di questi avanzerà mai l'enumeratore en. Il fatto che la chiamata Dispose nel secondo caso sia invocata su una copia di en non sarebbe un problema, poiché quella copia non fa nulla. Sfortunatamente, il modo in cui C# gestisce gli argomenti using struct-type interrompe il codice all'interno dell'istruzione using.

Problemi correlati