Aktualizacja: Moja oryginalna odpowiedź tak naprawdę nie odpowiedziała na pytanie, więc oto kolejna próba ...
Aby pomóc w rozwiązaniu problemu rootowego, programiści zapominają rzucić ObjectDisposedExceptions
, być może zautomatyzowane testowanie jednostkowe może załatwić sprawę. Jeśli chcesz być rygorystycznie wymagający, aby wszystkie metody/właściwości natychmiast rzuciły ObjectDisposedException
, jeśli Dispose
został już wywołany, możesz użyć następującego testu jednostki. Po prostu określ zespoły, które chcesz przetestować. Prawdopodobnie będziesz musiał zmodyfikować metodę IsExcluded
w razie potrzeby, a kpina z obiektu może nie działać we wszystkich przypadkach.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MbUnit.Framework;
using Moq;
[TestFixture]
public class IDisposableTests
{
[Test]
public void ThrowsObjectDisposedExceptions()
{
var assemblyToTest = Assembly.LoadWithPartialName("MyAssembly");
// Get all types that implement IDisposable
var disposableTypes =
from type in assemblyToTest.GetTypes()
where type.GetInterface(typeof(IDisposable).FullName) != null
select type;
foreach (var type in disposableTypes)
{
// Try to get default constructor first...
var constructor = type.GetConstructor(Type.EmptyTypes);
if (constructor == null)
{
// Otherwise get first parameter based constructor...
var constructors = type.GetConstructors();
if (constructors != null &&
constructors.Length > 0)
{
constructor = constructors[0];
}
}
// If there is a public constructor...
if (constructor != null)
{
object instance = Activator.CreateInstance(type, GetDefaultArguments(constructor));
(instance as IDisposable).Dispose();
foreach (var method in type.GetMethods())
{
if (!this.IsExcluded(method))
{
bool threwObjectDisposedException = false;
try
{
method.Invoke(instance, GetDefaultArguments(method));
}
catch (TargetInvocationException ex)
{
if (ex.InnerException.GetType() == typeof(ObjectDisposedException))
{
threwObjectDisposedException = true;
}
}
Assert.IsTrue(threwObjectDisposedException);
}
}
}
}
}
private bool IsExcluded(MethodInfo method)
{
// May want to include ToString, GetHashCode.
// Doesn't handle checking overloads which would take more
// logic to compare parameters etc.
if (method.Name == "Dispose" ||
method.Name == "GetType")
{
return true;
}
return false;
}
private object[] GetDefaultArguments(MethodBase method)
{
var arguments = new List<object>();
foreach (var parameter in method.GetParameters())
{
var type = parameter.ParameterType;
if (type.IsValueType)
{
arguments.Add(Activator.CreateInstance(type));
}
else if (!type.IsSealed)
{
dynamic mock = Activator.CreateInstance(typeof(Mock<>).MakeGenericType(type));
arguments.Add(mock.Object);
}
else
{
arguments.Add(null);
}
}
return arguments.ToArray();
}
}
Original Response: To nie wygląda jak coś jest jak NotifyPropertyWeaver dla IDisposable
więc jeśli chcesz, że trzeba stworzyć podobny projekt samodzielnie. Możesz potencjalnie zaoszczędzić sobie trochę pracy, mając podstawową klasę Jednorazowe, takie jak w tym blog entry. Wtedy masz tylko jedną linijkę u góry każdej metody: ThrowExceptionIfDisposed()
.
Jednak żadne z możliwych rozwiązań nie wydaje się właściwe ani konieczne. Ogólnie rzucanie ObjectDisposedException
nie jest tak często potrzebne. Zrobiłem szybkie wyszukiwanie w Reflectorze i ObjectDisposedException
jest wyrzucane bezpośrednio przez zaledwie 6 typów w BCL, a dla próbki poza BCL System.Windows.Forms
ma tylko jeden typ, który rzuca: Cursor
podczas uzyskiwania Uchwyt.
Zasadniczo wystarczy rzucić ObjectDisposedException
, jeśli wywołanie obiektu nie powiedzie się, ponieważ zostało już wywołane Dispose
, na przykład po ustawieniu pola na wartość null wymaganą przez metodę lub właściwość. Numer ObjectDisposedException
będzie bardziej pouczający niż losowy NullReferenceException
, ale dopóki nie wyczyścisz niezarządzanych zasobów, zwykle nie jest konieczne ustawienie grupy pól na wartość null. W większości przypadków, jeśli po prostu dzwonisz Wyrzuć na inne przedmioty, to do nich należy rzucanie ObjectDisposedException
.
Tutaj jest trywialny przykład można rzucać ObjectDisposedException
wyraźnie:
public class ThrowObjectDisposedExplicity : IDisposable
{
private MemoryStream stream;
public ThrowObjectDisposedExplicity()
{
this.stream = new MemoryStream();
}
public void DoSomething()
{
if (this.stream == null)
{
throw new ObjectDisposedException(null);
}
this.stream.ReadByte();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (this.stream != null)
{
this.stream.Dispose();
this.stream = null;
}
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
}
W powyższym kodzie, choć naprawdę nie ma potrzeby ustawić strumień null. można po prostu polegać na tym, że MemoryStream.ReadByte()
rzuci ObjectDisposedException
na swój własny jak poniższy kod:
public class ThrowObjectDisposedImplicitly : IDisposable
{
private MemoryStream stream;
public ThrowObjectDisposedImplicitly()
{
this.stream = new MemoryStream();
}
public void DoSomething()
{
// This will throw ObjectDisposedException as necessary
this.stream.ReadByte();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.stream.Dispose();
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
}
Pierwsza strategia ustalania strumień null może mieć sens w niektórych przypadkach, na przykład, jeśli wiesz, obiekt będzie wyrzuć wyjątek, jeśli Dispose zostanie wywołane wiele razy. W takim przypadku chcesz być defensywny i upewnić się, że twoja klasa nie rzuca wyjątku na wiele połączeń z numerem Dispose
. Poza tym nie mogę wymyślić żadnych innych przypadków, w których trzeba by ustawić pola na zero, co prawdopodobnie wymagałoby rzucenia ObjectDisposedExceptions
.
Rzucanie ObjectDisposedException
nie jest często potrzebne i należy je uważnie przeanalizować, więc narzędzie do tkania kodu prawdopodobnie nie jest konieczne. Korzystaj z bibliotek Microsoft jako przykładu, zobacz, które metody implementują w rzeczywistości ObjectDisposedException
, gdy typ implementuje IDisposable
.
Uwaga:GC.SuppressFinalize(this);
nie jest bezwzględnie konieczne, ponieważ nie ma finalizator, ale pozostało tam od podklasą mogą wdrożyć finalizatora. Jeśli klasa została oznaczona jako sealed
, można bezpiecznie usunąć GC.SupressFinalize
.
Nie znam żadnej z takich rzeczy ... Zazwyczaj dodaje się to ręcznie w prostszych klasach ... w klasach złożonych Mam centralną metodę wywołania 'Zapewnienie niezawrotności', która jest nazywana jako pierwsza w dowolnej metodzie i dba o to kilku istotnych aspektów, na które reszta klasy polega na ... że kontrola jest częścią mojego kodu 'Zapewnienie niezawisłości' ... – Yahia
Pisanie tego kodu jest bardzo silnym wskaźnikiem, że używasz IDisposable źle. Możesz ją zaimplementować tylko wtedy, gdy klasa ma pola typu implementującego IDisposable. Twoja metoda Dispose() po prostu wywołuje metodę Dispose(). Generowanie ObjectDisposedException jest zadaniem * tych * klas, nie twoich. Wprowadzasz jednorazowy wzorzec tylko wtedy, gdy twoja klasa ma finalizatora. To też robi źle, powinieneś zawsze zawinąć finalizowalny zasób we własnej klasie. Jak SafeHandle. –
@Hans Passant Twój argument nie ma sensu. Wprowadza nowy wektor błędu (ponieważ polega na tym, że ludzie używają poprawnie obiektu, na co prawie nigdy nie można polegać). Ostatecznie to twój obiekt jest rzeczą, która wie, czy dispose został wywołany, i musi zabronić nielegalnego korzystania z niego z komunikatem o błędzie (wyjątek tutaj), który kształci użytkowników w błąd ich sposobów. –