2015-07-24 37 views
6

Załóżmy, że mam typ struct wykonawczych IDisposible, i jeśli mogę użyć kodów poniżej:jest głęboki kopia typu struct usuwane także wtedy, gdy w bloku „Korzystanie ......”

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

oczywiście patrz po bloku użycia, ms jest usuwany. A co z strukturą w "InnerAction"? Czy wciąż jest żywy z powodu głębokiej kopii, czy też jest zbyty?

Jeśli nadal jest żywy (nie utylizowany), czy muszę użyć "ref" dla "InnerAction"?

Podaj mi swój dowód :)

Thx all.

+1

Nie ma "głębokiej kopii" i dlatego nic nie zostało do dyspozycji. – Henrik

+11

Typy 'IDisposable' to [zły pomysł] (http://ericlippert.com/2011/03/14/to-box-or-not-to-box/) ... – xanatos

+1

Zmienne struktury są złe idea] (http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil) Aby implementować sugestie jednorazowe muszą mieć stan, a więc muszą być zmienne. – weston

Odpowiedz

4

Jest gorszy niż myślisz: ms nie jest nawet utylizowany.

Powód jest taki, że instrukcja using tworzy wewnętrzną kopię, którą wywołuje w konstrukcie try/finally.

Rozważ to 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; 
    } 
} 

Oto niektóre z decompiled IL:

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 

Zauważ, że w IL_000E kompilator generowane lokalny (CS$3$0001) jest tworzony i kopia ms jest tam przechowywana . Później ...

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 nazywa się przed tym lokalnym, nie ms (który jest przechowywany w lokalizacji 0).

Powoduje to, że zarówno ms, jak i kopia, która zatrzymuje się na kopii, nie są ułożone.

Wniosek: nie używaj struktur w instrukcjach using.

EDYCJA: jak @Weston wskazuje w komentarzach, you can manually box the struct and act on the boxed instance, ponieważ wtedy żyje na stercie. W ten sposób możesz pobrać instancję, ale jeśli ją odrzucisz do struktury w oświadczeniu using, skończy się przechowywanie kopii przed jej usunięciem. Co więcej, boks usuwa korzyści z pozostawania poza stosem, co prawdopodobnie należy do tej pory.

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

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

Oto, co wymyśliłem, ale potem zobaczyłem to: http://stackoverflow.com/a/1330596/360211 Mówi "typy wartości nie są zapakowane w użytek" ... – weston

+0

Nie są zapakowane w sposób że "używanie" odnosi się do obiektu na stercie. Powyższa IL pokazuje to. – codekaizen

+0

O tak, myślę, że boks może uratować sytuację, ponieważ * może * odnosić się do tej samej instancji. – weston

2

Zachowanie twojego kodu zależy od wewnętrznej implementacji MyStruct.

Rozważmy następujący realizacji:

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; 
    } 
} 

w powyższym kodzie, realizacja MyStruct tylko delegatów Usunąć połączenia do innych typów referencyjnych. W takim przypadku instancja w twoim przykładzie może być uznana za "Zorganizowaną" po zakończeniu bloku using. Podobne zachowanie można osiągnąć, zapisując wewnętrzne odniesienie do elementu boolowskiego, wskazując, czy klasa jest zbywana.

Jednak w przykładach w odpowiedzi @ codekaizena iw komentarzu @ xanatos, zachowanie polega na tym, że tylko kopia jest usuwana, jak tam wskazano.

Najważniejsze jest to, że możesz sprawić, by twój układ zachowywał się poprawnie zgodnie ze schematem Disposed, ale unikałbym tego, ponieważ jest bardzo podatny na błędy.

+1

To jest dobry wgląd i zasadniczo wzorzec używany przez 'System.Threading.CancellationToken', gdzie służy do zapewnienia bezpiecznego dostępu do stanu współdzielonego. – codekaizen

0

myślę, że to niedobrze, że realizatorzy z C# zdecydował, że zatrudniając using o strukturze powinny powodować wszystkie metody na tej struktury (w tym Dispose) do otrzymania kopii tego, ponieważ takie zachowanie prowadzi do wolniejszego kodu niż działające na oryginale , wyklucza to, co w innym przypadku byłoby pożyteczną semantyką, i nie w żadnym wypadku nie mogę zidentyfikować, aby prowadzić do tego, co w innym przypadku byłby nieprawidłowy kod działający poprawnie. Niemniej jednak zachowanie jest tym, czym jest.

W związku z tym sugeruję, że żadna struktura nie powinna implementować IDisposable w żaden sposób, który ma modyfikować samą strukturę. Jedyne rodzaje konstrukcji, które wdrażają IDisposable powinny mieścić jeden lub oba z następujących wzorów:

  1. Konstrukcja służy do hermetyzacji niezmiennej odniesienie do obiektu, a struktura zachowuje się tak, jakby stan ten obiekt jako własny. Nie mogę myśleć o tym, gdzie widziałem ten wzór używany do enkapsulacji obiektów wymagających usuwania, ale wydawałoby się to możliwe.

  2. Typ struktury implementuje interfejs dziedziczący IDisposable, a niektóre z jego implementacji wymagają czyszczenia. Jeśli struktura sama w sobie nie wymaga czyszczenia, a metoda jej usuwania nic nie robi, to fakt, że metoda wywoływania jest wywoływana na kopii, nie miałaby znaczenia poza faktem, że system straci czas na wykonanie bezużytecznej kopii struktury przed wywołaniem -nasza metoda na tym.

Zauważ, że zachowanie using rachunku C# 's powoduje problemy nie tylko jeśli chodzi o Dispose, ale także jeśli chodzi o wezwaniem innymi metodami. Rozważ:

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); 
    } 
} 

Podczas gdy dwie metody leżały wyglądają równoznacznie, pierwsze zadziała, a drugie nie. W pierwszej metodzie każde wywołanie do MoveNext będzie działało na zmienną en, a zatem będzie postępowało z modułem wyliczającym. W drugim każde wywołanie do MoveNext będzie działać na innej kopii en; żaden z nich nigdy nie awansuje do modułu wyliczającego en. Fakt, że wywołanie Dispose w drugim przypadku jest wywoływane na kopii en, nie stanowiłby problemu, ponieważ ta kopia nic nie robi. Niestety sposób, w jaki C# obsługuje argumenty typu struct using, również łamie kod w instrukcji using.