DI: 处理 IDisposable 对象的生命周期

8
我正在开发一个DI/IoC容器OpenNETCF.IoC,与此相关的是一个(合理的)功能请求,要为容器集合中的IDisposable项目添加某种生命周期管理。
我的当前想法是,由于我无法查询对象以查看它是否已被处理,并且我无法获取有关其何时被处理的事件,因此我必须为开发人员希望框架管理的对象创建某种包装器。
现在,可以使用AddNew(为简单起见,我们将假设只有一个重载,并且没有Add)添加对象:
public TTypeToBuild AddNew<TTypeToBuild>() { ... }

我正在考虑添加一个新的方法(其实是一组方法,但你应该明白):
public DisposableWrappedObject<IDisposable> AddNewDisposable<TTypeToBuild>()
    where TTypeToBuild : class, IDisposable
{
    ...
}

DisposableWrappedObject看起来像这样:

public class DisposableWrappedObject<T>
    where T : class, IDisposable
{
    public bool Disposed { get; private set; }
    public T Instance { get; private set; }

    internal event EventHandler<GenericEventArgs<IDisposable>> Disposing;

    internal DisposableWrappedObject(T disposableObject)
    {
        if (disposableObject == null) throw new ArgumentNullException();

        Instance = disposableObject;
    }

    ~DisposableWrappedObject()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        lock(this)
        {
            if(Disposed) return;

            EventHandler<GenericEventArgs<IDisposable>> handler = Disposing;
            if(handler != null)
            {
                Disposing(this, new GenericEventArgs<IDisposable>(Instance));
            }

            Instance.Dispose();

            Disposed = true;
        }
    }
}

现在,当通过AddNewDisposable向容器添加项目时,还会添加事件处理程序,以便在通过包装器处置它(从底层集合中)时,框架会将其删除。
我已经实现了这个功能,并且通过了单元测试,但我想知道在哪里可能出错,或者如何使其更加“友好”于使用者。
编辑1
由于有人问到如何使用Disposing事件,这里有一些代码(仅保留重要部分):
private object AddNew(Type typeToBuild, string id, bool wrapDisposables)
{
    ....

    object instance = ObjectFactory.CreateObject(typeToBuild, m_root);

    if ((wrapDisposables) && (instance is IDisposable))
    {
        DisposableWrappedObject<IDisposable> dispInstance = new
               DisposableWrappedObject<IDisposable>(instance as IDisposable);
        dispInstance.Disposing += new 
               EventHandler<GenericEventArgs<IDisposable>>(DisposableItemHandler);
        Add(dispInstance as TItem, id, expectNullId);
        instance = dispInstance;
    }

    ....

    return instance;
}

private void DisposableItemHandler(object sender, GenericEventArgs<IDisposable> e)
{
    var key = m_items.FirstOrDefault(i => i.Value == sender).Key;
    if(key == null) return;
    m_items.Remove(key);
}

我们能否获得更详细的描述正在添加的特定功能?人们想要这个功能的用例是什么,事件处理程序是为您作为(IoC框架)还是最终用户而设计的?等等。 - Quibblesome
用例是添加自动生命周期管理。如果将一个IDisposable项添加到集合中,然后调用Dispose,它实际上永远不会被清理,因为容器保留了对象的根。这个想法是你可以在不必返回到集合中查找它的情况下对对象进行Dispose,并且会自动从容器集合中移除。该事件仅在框架内部使用(甚至标记为内部以避免在外部使用),处理程序会将其从集合中删除。 - ctacke
我已更新问题以增加事件处理的清晰度。 - ctacke
那么消费者已经处理了对象,并且他们想确保您不再持有它?如果他们再次请求它会发生什么,您会创建一个新的吗?就代码而言,我唯一注意到的不寻常之处是您正在通过事件处理程序关闭终结器的GC线程,因此请小心使用该代码! :) - Quibblesome
如果您将一个IDisposable项添加到集合中,并稍后调用Dispose,则它实际上永远不会被清理,因为容器持有对象的根引用。仅保留引用适用于GC。处理非托管资源应该是开发人员的责任。无论存在什么其他引用,调用Dispose都将立即执行清理。 - TrueWill
是的,它会清理任何本机资源,但问题的关键在于对象本身,包括其所有托管资源,永远不会被释放,这就是我试图解决的根源问题。如果是像Compact Framework应用程序中的UserControl或Form这样的东西,这可能是一个问题。移动设备根本没有足够的资源来容纳程序员的懒惰。你不能像在桌面上那样把一切都扔进容器里,然后不用担心自己的清理工作。 - ctacke
1个回答

3

啊,但是如果使用as-cast呢?我知道它是IDisposable类型的,并且必须放入容器中,但我不能返回该容器,因为AddNew<>方法必须返回输入类型。如果我直接按照API要求返回对象,则他们不知道它被包装了,然后只调用实例而不是容器上的Dispose方法,再次出现持有已释放对象但从未被GC回收的问题。 - ctacke
5
在使用IoC时,消费者必须遵循一些规则:当您将创建对象的责任移交给DI容器时,必须将所有生命周期管理(包括实例销毁)都移交给它。因此,如果消费者过早地释放注入的依赖项,则会出现错误,因为该实例可能在多个消费者之间共享。我认为您不需要过度复杂化API以防止使用方式明显错误的情况。 - Mark Seemann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接