我最近遇到并开始使用notifypropertyweaver,它自动填写调用PropertyChanged处理程序的所有样板代码(作为msbuild任务,因此不需要额外的运输依赖).我想知道是否有人知道标准配置模式的类似解决方案.它本质上要做的是接受一个变量名称作为配置(在MSDN的示例中布置bool),并将以下代码添加到每个不是Finalizer的方法中,或者在实现IDisposable的每个类中命名为Dispose:
if(disposed) throw new ObjectDisposedException();
这样的事情存在吗?或者人们如何在他们的代码中实现这一点,手动添加if语句?
澄清目的
对此的更大需求不是某些“最佳实践”驱动器,而是我们确实让用户不正确地管理对象的生命周期.现在他们只是从底层资源获得NullReference或其他一些这样的东西,这可能意味着我们的库中有一个bug,我想告诉他们他们是创建问题的人以及他们如何装箱(考虑到我我知道的位置.因此,建议我们的类型的用户应该是应该照顾的人,这里的效率并不高.
解决方法
为了帮助解决根问题,开发人员忘记抛出ObjectDisposedExceptions,也许自动单元测试就可以了.如果您想严格要求所有方法/属性在已经调用Dispose时立即抛出ObjectDisposedException,那么您可以使用以下单元测试.只需指定要测试的程序集即可.您可能需要根据需要修改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(); } }
原始响应:看起来像NotisPropertyWeaver的IDisposable似乎没有,所以如果你想要,你需要自己创建一个类似的项目.你可以通过拥有像这个blog entry这样的基础Disposable类来节省一些工作.然后你只需要在每个方法的顶部有一个单行:ThrowExceptionIfDisposed().
但是,两种可能的解决方案都不正确或似乎没有必通常,通常不需要抛出ObjectDisposedException.我在Reflector中进行了快速搜索,并且在BCL中只有6种类型直接抛出了ObjectDisposedException,对于BCL System.Windows.Forms之外的示例,它只有一种类型抛出:获取Handle时的Cursor.
基本上,如果对象的调用因为Dispose已被调用而特别失败,则只需要抛出ObjectDisposedException,例如,如果将某个字段设置为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的库作为示例,当类型实现IDisposable时,请查看实现实际抛出ObjectDisposedException的方法.
注意:GC.SuppressFinalize(this);因为没有终结器,所以不是严格需要的,但是由于子类可以实现终结器,所以它留在那里.如果该类被标记为已密封,则可以安全地删除GC.SupressFinalize.