Ale*_*x K 7 c# dispose code-injection
我最近一直在阅读Effective C#和其他一些这样的书籍/博客,当谈到标准的Dispose模式(我已经使用过)时,他们都建议使用类' dispose变量(在MSDN示例代码中定义),每个方法的开头.基本上确保一旦调用Dispose,任何使用该对象的尝试都将导致ObjectDisposedException.这是有道理的,但是在足够大的代码库中有大量的手工劳动,并依赖于人们记住这样做.所以我正在寻找更好的方法.
我最近遇到并开始使用notifypropertyweaver,它自动填写调用PropertyChanged处理程序的所有样板代码(作为msbuild任务,因此不需要额外的运输依赖).我想知道是否有人知道标准配置模式的类似解决方案.它本质上要做的是接受一个变量名作为config(bool disposed在MSDN的示例中),并将以下代码添加到每个不是Finalizer的方法中,或者在实现IDisposable的每个类中命名为Dispose:
if(disposed)
throw new ObjectDisposedException();
Run Code Online (Sandbox Code Playgroud)
这样的事情存在吗?或者人们如何在他们的代码中实现这一点,手动添加if语句?
澄清目的
对此的更大需求不是某种"最佳实践"驱动,而是我们确实让用户不正确地管理我们对象的生命周期.现在他们只是从底层资源获得NullReference或其他一些这样的东西,这可能意味着我们的库中有一个bug,我想告诉他们他们是创建问题的人以及他们如何创建它(考虑到我我知道的位置.因此,建议我们的类型的用户应该是应该照顾的人,这里的效率并不高.
更新:我原来的回复并没有真正回答这个问题,所以这是另一个尝试......
为了帮助解决根本问题,开发人员忘记抛出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();
}
}
Run Code Online (Sandbox Code Playgroud)
原始响应:看起来没有像 NotifyPropertyWeaver 这样的东西,IDisposable所以如果您愿意,您需要自己创建一个类似的项目。通过拥有一个基本的 Disposable 类(如本博客文章中所示),您可能会节省一些工作量。然后你就可以在每个方法的顶部添加一行:ThrowExceptionIfDisposed()。
然而,这两种可能的解决方案都感觉不正确或似乎没有必要。一般来说,投掷ObjectDisposedException并不需要那么频繁。我在 Reflector 中进行了快速搜索,发现ObjectDisposedExceptionBCL 中只有 6 种类型会直接抛出 ,而对于 BCL 之外的示例,System.Windows.Forms只有一种类型会抛出:Cursor获取句柄时。
ObjectDisposedException基本上,只有当对对象的调用因已被调用而失败时,才需要抛出异常Dispose,例如,如果您将方法或属性所需的某个字段设置为 null。AnObjectDisposedException比 random 提供更多信息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);
}
}
Run Code Online (Sandbox Code Playgroud)
通过上面的代码,实际上不需要将流设置为空。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);
}
}
Run Code Online (Sandbox Code Playgroud)
在某些情况下,将 Stream 设置为 null 的第一个策略可能是有意义的,例如,如果您知道多次调用 Dispose 时该对象将引发异常。在这种情况下,您需要采取防御措施并确保您的类不会在多次调用Dispose. 除此之外,我想不出任何其他需要将字段设置为 null 的情况,这可能需要 throw ObjectDisposedExceptions。
抛出 anObjectDisposedException并不经常需要,应该仔细考虑,因此您想要的代码编织工具可能不是必需的。使用 Microsoft 的库作为示例,查看ObjectDisposedException当类型实现IDisposable.
注意: GC.SuppressFinalize(this);并不是严格需要的,因为没有终结器,但它被留在那里,因为子类可能实现终结器。如果该类被标记为sealed则GC.SupressFinalize可以安全地删除。
| 归档时间: |
|
| 查看次数: |
659 次 |
| 最近记录: |