如何处理不知道结果是否产生的 IDisposable 对象的 IEnumerable?

How to handle an IEnumerable of IDisposable objects not knowing if the results are yield or not?

我正在寻找处理这种情况的最佳实践/标准。

我们的代码 (MyClass) 使用另一个 class (ItemGenerator)。 ItemGenerator 对我们来说是一个黑盒子,所以我们不知道实现(我们知道但我们不想依赖它,因为它可能会从下面改变)。

ItemGenerator 有一个方法 GetItems(),它 return 是 Item 的一个 IEnumerable。 项目 class 实现了 IDisposable,因此我们应该在完成后释放该对象。

当我们(MyClass)遍历项目列表时,如果发生异常(任何异常),我们希望停止处理并释放控制(将异常冒泡)。

我的问题是:

我们是否应该不断迭代这些项目以处理所有项目?这可能看起来很愚蠢,但如果不处理其余物品会怎样?

同时,根据下面的代码,我们绝对不应该迭代其余项目,因为它们是 yield return。那么为什么要生成它们以便我们可以处理它们(它可能会显着影响性能)。

问题是我们不知道 GetItems() return 是否是按需(产量)项目。而且我认为我们不应该关心,对吗?

那么我们应该如何处理链表中间出现异常的情况(举例)?

下面是说明其要点的代码示例。

这是我们的代码:

public class MyClass
{
    public void VerifyAllItems()
    {
        ItemGenerator generator = new ItemGenerator();

        foreach (Item item in generator.GetItems())
        {
            try
            {

                // Do some work with "item" here. Though an exception could occur.
                // If an exception occurs, we don't care about processing the rest of the items and just want to bubble up the exception

            }
            finally
            {
                // Always dispose of the 
                item?.Dispose();
            }
        }
    }
}

这是黑盒代码

public class ItemGenerator
    {
        private long _itemsToGenerate = 0;
        public ItemGenerator()
        {
            _itemsToGenerate = new Random().Next(10, 100);
        }

        public IEnumerable<Item> GetItems()
        {
            while (_itemsToGenerate > 0)
            {
                yield return HeavyWork();
                _itemsToGenerate--;
            }
        }

        private Item HeavyWork()
        {
            // Doing a lot of work here
            return new Item();
        }
    }

    public class Item : IDisposable
    {
        private bool _isDisposed = false;

        public virtual void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool isDisposing)
        {
            if (!_isDisposed)
            {
                if (isDisposing)
                {
                    // Dispose of any resources
                }

                _isDisposed = true;
            }
        }
    }

问题比你说的还要严重。不仅不能确定是否枚举集合,根本就不能确定是否要处理掉 any 的项目。仅仅因为某些东西实现了 IDisposable 并不意味着你应该处理它,例如如果您的工厂总是 returns 相同的实例。

这种问题正是 分配某些东西的代码通常负责释放它的原因。在这种情况下,ItemGenerator 会创建项目,因此它应该处理它们。

class ItemGenerator : IDiposable
{
    protected readonly List<Item> _instances = new List<Item>();

    IEnumerable<Item> GetItems()
    {
        for ( some; condition; here; )
        {
            var item = new Item();
            _instances.Add(item);
            yield return item;
        }
    }

    public void Dispose()
    {
        foreach (var item in _instances) item.Dispose();
    }
}    

现在您只需将 ItemGenerator 放入 using 块中即可。

public void VerifyAllItems()
{
    using (ItemGenerator generator = new ItemGenerator())
    {
        foreach (Item item in generator.GetItems())
        {
            try
            {
                // Do some work with "item" here. Though an exception could occur.
                // If an exception occurs, we don't care about processing the rest of the items and just want to bubble up the exception

            }
            finally
            {
                //Don't need to dispose anything here
            }
        } 
    } //Disposal happens here because of the using statement
}
    

使用此模式,当您退出 using 块时,由 ItemGenerator 分配的任何项目都将被释放。

现在调用者根本不需要关心项目生成器的实现,或者担心处置生成器本身以外的任何东西。当然,如果您是分配生成器的人,您应该处理掉生成器。

有点奇怪,但是...

internal class DisposingEnumerator : IEnumerator<Item>
{
    private readonly List<IDisposable> deallocationQueue = new List<IDisposable>();
    private readonly IEnumerable<Item> source;
        
    private IEnumerator<Item> sourceEnumerator;
        
    public DisposingEnumerator(IEnumerable<Item> source)
    {
        this.source = source;
    }

    public bool MoveNext()
    {
        if (sourceEnumerator == null)
        {
            sourceEnumerator = source.GetEnumerator();
        }
        bool hasNext = sourceEnumerator.MoveNext();
        if (hasNext)
        {
            deallocationQueue.Add(Current);
        } 
        return hasNext;
    }

    public Item Current => sourceEnumerator.Current;

    object IEnumerator.Current => Current;

    public void Reset()
    {
        throw new NotSupportedException();
    }

    // Will be called within "foreach" statement
    // You can implement IDisposable in ItemCollection as well
    public void Dispose()
    {
        foreach (var item in deallocationQueue)
        {
            item.Dispose();
        }
    }
}

public class ItemCollection : IEnumerable<Item>
{
    private IEnumerator<Item> enumerator;

    public ItemCollection(IEnumerable<Item> source)
    {
        this.enumerator = new DisposingEnumerator(source);
    }

    public IEnumerator<Item> GetEnumerator()
    {
        return enumerator;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

用法:

var items = generator.GetItems();
// Equivalent of using statement
foreach (var item in new ItemCollection(items))
{

}

Proof