Code weave helper用于标准的Dispose模式?

时间:2012-01-21 14:57:54

标签: c# dispose code-injection

我最近一直在阅读Effective C#和其他一些此类书籍/博客,当谈到standard Dispose pattern(我已经在使用)时,他们都建议使用“ dispose 每个方法开头的变量(在MSDN示例代码中定义)。基本上确保一旦调用Dispose,任何使用该对象的尝试都将导致ObjectDisposedException。这是有道理的,但是在足够大的代码库中有大量的手工劳动,并依赖于人们记住这样做。所以我正在寻找更好的方法。

我最近遇到并开始使用notifypropertyweaver自动填写调用PropertyChanged处理程序的所有样板代码(作为msbuild任务,因此不需要额外的运输依赖性)。我想知道是否有人知道标准配置模式的类似解决方案。它本质上要做的是接受变量名称作为配置(MSDN示例中的bool disposed),并将以下代码添加到每个不是Finalizer的方法中,或者在实现IDisposable的每个类中命名为Dispose: / p>

if(disposed)
  throw new ObjectDisposedException();

这样的事情存在吗?或者人们如何在他们的代码中实现这一点,手动添加if语句?

澄清目的
对此的更大需求不是某些“最佳实践”驱动器,而是我们确实让用户不正确地管理对象的生命周期。现在他们只是从底层资源获得NullReference或其他一些这样的东西,这可能意味着我们的库中有一个bug,我想告诉他们他们是创建问题的人以及他们如何创建它(考虑到我我知道的位置。因此,建议我们的类型的用户应该是应该照顾的人,在这里效率不高。

2 个答案:

答案 0 :(得分:2)

我建议您以另一种方式解决问题,而不是使用NotifyPropertyWeaver。您声称有两个问题:

  1. 人类将忘记实施模式
  2. 您希望避免在大型代码库中多次实施它的成本
  3. 为了支付重写非常相似的代码的成本,我建议您在Visual Studio中创建和使用代码片段。片段寻求解决的问题恰好是上面列表中的#2。有关说明,请参阅此MSDN article

    上面列表中#1的问题是100%的类并不总是需要IDisposable模式。这使得静态分析难以“捕获”给定类是否应该是一次性的。 (编辑:在考虑了一分钟之后,它实际上并不难检查。如果你当前的类包含IDisposable的实例,你的类应该是IDisposable)

    因此,我建议您使用代码审查来捕获开发人员应该使他们的类一次性使用的情况。您可以将其添加到开发人员应该自行检查的项目清单中,然后再要求进行代码审查。

答案 1 :(得分:2)

更新:我的原始回复并没有真正回答这个问题,所以这是另一次尝试......

为了帮助解决根问题,开发人员忘记抛出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();
    }
}

原始回复IDisposable看起来没有像NotifyPropertyWeaver这样的东西,所以如果你想要,你需要自己创建一个类似的项目。通过使用像blog entry这样的基础Disposable类,您可以节省一些工作。然后,您只需在每个方法的顶部使用一行代码:ThrowExceptionIfDisposed()

然而,两种可能的解决方案都没有正确或似乎是必要的。通常情况下,不需要投掷ObjectDisposedException。我在Reflector中进行了快速搜索,并且在{BCL}中只有6种类型直接抛出ObjectDisposedException,对于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);
    }
}

在某些情况下,将stream设置为null的第一个策略可能有意义,例如,如果您知道如果多次调用Dispose,则该对象将抛出异常。在这种情况下,您希望保持防御,并确保您的类不会在多次调用Dispose时抛出异常。除此之外,我无法想到你需要将字段设置为null的任何其他情况,这可能需要抛出ObjectDisposedExceptions

经常不需要投掷ObjectDisposedException并且应该仔细考虑,因此您可能不需要代码编织工具。使用Microsoft的库作为示例,当类型实现ObjectDisposedException时,查看实际实现的方法抛出IDisposable

注意: GC.SuppressFinalize(this);并不是严格需要的,因为没有终结器,但由于子类可以实现终结器,所以它留在那里。如果该类被标记为sealed,则可以安全删除GC.SupressFinalize