Обновление: Мой первоначальный ответ на самом деле не ответил на вопрос, поэтому вот еще одна попытка ...
Чтобы помочь решить корневую проблему, разработчики забыли бросить ObjectDisposedExceptions
,возможно, автоматическое модульное тестирование поможет.Если вы хотите строго соблюдать требование, чтобы все методы / свойства немедленно генерировали ObjectDisposedException
, если Dispose
уже был вызван, то вы можете использовать следующий модульный тест.Просто укажите сборки, которые вы хотите проверить.Вероятно, вам потребуется изменить метод IsExcluded
, если необходимо, и имитация объекта может работать не во всех случаях.
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();
}
}
Исходный ответ : не выглядиткак будто есть что-то вроде NotifyPropertyWeaver для IDisposable
, поэтому, если вы хотите, вам нужно создать подобный проект самостоятельно.Потенциально вы можете сэкономить небольшую работу, имея базовый класс Disposable, как в этой записи блога .Тогда у вас есть только одна строка в верхней части каждого метода: ThrowExceptionIfDisposed()
.
Однако ни одно из возможных решений не кажется правильным и не представляется необходимым.В общем, бросать ObjectDisposedException
не нужно так часто.Я выполнил быстрый поиск в Reflector, и ObjectDisposedException
генерируется непосредственно только 6 типами в BCL, а для образца вне BCL System.Windows.Forms
имеет только один тип, который выбрасывает: Cursor
при получении дескриптора.
По сути, вам нужно только выбросить ObjectDisposedException
, если вызов вашего объекта завершится неудачей, в частности, потому что Dispose
уже был вызван, например, если вы установите какое-то поле в null, которое требуется для метода или свойства,ObjectDisposedException
будет более информативным, чем случайное NullReferenceException
, но, если вы не очищаете неуправляемые ресурсы, обычно нет необходимости устанавливать для набора полей значение null.В большинстве случаев, если вы просто вызываете Dispose для других объектов, они могут бросить ObjectDisposedException
.
Вот простой пример, который вы можете явно бросить ObjectDisposedException
:
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);
}
}
С кодом выше, хотя на самом деле нет необходимости устанавливать поток в нуль.Вы можете просто положиться на тот факт, что MemoryStream.ReadByte()
сгенерирует ObjectDisposedException
самостоятельно, как показано в коде ниже:
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);
}
}
Первая стратегия установки нулевого потока в некоторых случаях может иметь смысл, например:если вы знаете, что объект вызовет исключение, если Dispose вызывается несколько раз.В этом случае вы хотите проявить оборону и убедиться, что ваш класс не выдает исключение при множественных вызовах на Dispose
.Кроме того, я не могу думать о каких-либо других случаях, когда вам нужно было бы установить нулевые поля, которые, вероятно, потребовали бы броска ObjectDisposedExceptions
.
Бросок ObjectDisposedException
не требуется часто и долженТщательно продуманный, поэтому инструмент для создания кода, который вам нужен, вероятно, не нужен.Используйте библиотеки Microsoft в качестве примера, посмотрите, какие методы, которые реализуют, на самом деле выдают ObjectDisposedException
, когда тип реализует IDisposable
.
Примечание: GC.SuppressFinalize(this);
строго не требуется, так как естьнет финализатора, но он остался там, так как подкласс может реализовывать финализатор.Если класс был помечен как sealed
, тогда GC.SupressFinalize
можно было бы безопасно удалить.