代码编织助手的标准配置模式?

我最近在阅读有效的C#和其他一些书籍/博客,当谈到标准的Dispose模式(我已经使用它)时,他们都建议使用类的dispose变量(如MSDN示例代码中定义的)每种方法的开始。 基本上要确保一旦调用Dispose,任何使用该对象的尝试都将导致ObjectDisposedException。 这是有道理的,但是在足够大的代码库中需要大量的体力劳动,并且依靠人们记住这么做。 所以我正在寻找更好的方法。

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

if(disposed)
  throw new ObjectDisposedException();

这样的事情存在吗? 另外人们在代码中做了什么来实现这一点,手动添加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();
    }
}

原始响应 :对于IDisposable ,看起来没有像NotifyPropertyWeaver这样的东西,所以如果你想要的话,你需要自己创建一个类似的项目。 通过在此博客条目中添加基本的Disposable类,您可以为自己节省一点工作。 然后,您只需在每个方法的顶部添加ThrowExceptionIfDisposed()ThrowExceptionIfDisposed()

然而,没有任何可能的解决方案是正确的或看起来是必要 通常不需要抛出ObjectDisposedException 。 我在Reflector中进行了一次快速搜索,并且ObjectDisposedException由BCL中的6种类型直接引发,并且在BCL System.Windows.Forms之外的示例中仅引发了一种类型System.Windows.Forms只有一种类型在获取句柄时抛出: Cursor

基本上,只需要抛出ObjectDisposedException如果对象上的调用会失败,因为Dispose已经被调用,例如,如果将方法或属性所需的某些字段设置为null。 ObjectDisposedException将比随机NullReferenceException更具信息性,但除非您清理非托管资源,否则通常不需要将一堆字段设置为null。 大多数情况下,如果您只是在其他对象上调用Dispose, ObjectDisposedException他们来抛出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); 不是完全需要的,因为没有终结器,但由于子类可能实现终结器,所以它仍然存在。 如果该类被标记为sealed则可以安全地删除GC.SupressFinalize


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

  • 人类会忘记实施这一模式
  • 您希望避免在大型代码库中多次实施它的成本
  • 为了支付重写非常相似代码的代价,我建议您在Visual Studio中创建和使用代码片段。 片段试图解决的问题正是上面列表中的#2。 有关说明,请参阅此MSDN文章。

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

    正因为如此,我建议您使用Code Review来捕捉开发人员应该让他们的班级一次性使用的情况。 您可以将其添加到开发人员在请求代码审查之前应自行检查的项目清单中。

    链接地址: http://www.djcxy.com/p/57277.html

    上一篇: Code weave helper for the standard Dispose pattern?

    下一篇: Characters overlapping when they have changed color and are printed backwards