最近,我一直在阅读有效的C#和其他一些这样的书籍/博客,当谈到标准配置模式 (我已经在使用)时,他们都建议在每个方法的开头使用类的dispose变量(在MSDN示例代码中定义的)。本质上,为了确保一旦调用了Dispose,任何使用该对象的尝试都会导致ObjectDisposedException。这是合理的,但在足够大的代码库中是大量的手工劳动,并且依赖于人类记住这样做。所以我在寻找更好的方法。
我最近发现并开始使用通知织器,它自动填充调用PropertyChanged处理程序的所有样板代码(作为msbuild任务工作,因此不需要额外的传递依赖项)。我想知道是否有人知道一个类似的解决方案的标准处理模式。它实际上要做的是接受一个变量名作为配置(在MSDN的示例中是bool disposed ),并在每个实现IDisposable的类中将以下代码添加到每个不是终结器或命名Dispose的方法中:
if(disposed)
throw new ObjectDisposedException();这样的东西存在吗?或者,人们如何在他们的代码中实现这一点,手动添加if-语句?
澄清目的
对此的更大需求不是一些“最佳实践”驱动,而是我们确实让用户不恰当地管理对象的生命周期。现在,他们只是从底层资源中得到一个NullReference或其他类似的东西,这可能意味着我们的库中有一个bug,我想告诉他们,是他们制造了这个问题,以及他们是如何处理这个问题的(考虑到我已经准备好了)。因此,我们这类的用户应该负责处理这个问题的建议,在这里并不是真正有效的。
发布于 2012-01-26 08:15:01
更新:我最初的回答并没有真正回答这个问题,所以这里是另一个尝试.
为了帮助解决根本问题,开发人员忘记抛出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 for IDisposable的东西,所以如果您愿意,您需要自己创建一个类似的项目。您可以通过在这个博客条目中拥有一个基本的可处理类来节省一些工作。然后,在每个方法的顶部都有一个一行:ThrowExceptionIfDisposed()。
然而,任何可能的解决方案都不觉得是对的,也似乎是必要的。通常,抛出ObjectDisposedException并不是经常需要的。我在Reflector中进行了快速搜索,ObjectDisposedException仅由BCL中的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);
}
}使用上面的代码,实际上不需要将流设置为null。您可以只依赖于这样一个事实,即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);
}
}在某些情况下,将流设置为null的第一种策略可能是有意义的,例如,如果您知道如果多次调用Dispose,则对象将抛出异常。在这种情况下,您希望保持防御,并确保类不会在多次调用Dispose时抛出异常。此外,我想不出任何其他情况,您需要将字段设置为null,这可能需要抛出ObjectDisposedExceptions。
抛出ObjectDisposedException并不是经常需要的,应该仔细考虑,所以您想要的代码编织工具可能不是必要的。以Microsoft的库为例,查看在类型实现ObjectDisposedException时实际抛出IDisposable的哪些方法。
注意:严格来说,不需要 GC.SuppressFinalize(this);,因为没有终结器,但是因为子类可能实现终结器,所以它就留在那里了。如果类被标记为sealed,那么可以安全地删除GC.SupressFinalize。
发布于 2012-01-25 16:30:52
我建议您以另一种方式解决您的问题,而不是使用NotifyPropertyWeaver。你声称有两个问题:
为了支付重写非常类似的代码的成本,我建议您在Visual中创建和使用代码片段。代码片段寻求解决的问题正是上面列表中的#2。有关说明,请参阅此MSDN文章。
上面列表中的#1的问题是,在您的类中,IDisposable模式并不总是必需的。这使得静态分析很难“捕捉”给定的类是否应该是一次性的。(编辑:经过一分钟的思考,它实际上不会有那么困难的检查。如果当前类包含IDisposable实例,那么类应该是IDisposable)
正因为如此,我建议您使用代码评审来捕捉开发人员应该使类一次性使用的情况。您可以将其添加到开发人员在要求进行代码检查之前应该自我检查的项目清单中。
https://stackoverflow.com/questions/8953951
复制相似问题