【问题标题】:How to handle an IEnumerable of IDisposable objects not knowing if the results are yield or not? [closed]如何处理不知道结果是否为产量的 IDisposable 对象的 IEnumerable? [关闭]
【发布时间】:2021-12-14 18:18:53
【问题描述】:

我正在寻找如何处理这种情况的最佳做法/标准。

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

ItemGenerator 有一个方法 GetItems(),它返回 Item 的 IEnumerable。 Item 类实现了 IDisposable,因此我们应该在完成后处理该对象。

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

我的问题是:

我们是否应该继续遍历这些项目以处理所有项目?这可能看起来很愚蠢,但如果其他物品不被处理掉怎么办?

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

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

那么当列表中间出现异常(例如)时,我们应该如何处理呢?

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

这是我们的代码:

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;
            }
        }
    }

【问题讨论】:

  • 你想多了。如果提供者需要您在遗弃方面做额外的工作,那它就坏了。如果没有,那么什么都不做是正确的。

标签: c# yield idisposable


【解决方案1】:

问题比你说的还要严重。您不仅无法确定是否要枚举集合,而且您根本无法确定是否要处理 any 项。仅仅因为某些东西实现了IDisposable 并不意味着你应该处理它,例如如果你有一个总是返回相同实例的工厂。

这类问题正是分配某物的代码通常负责解除分配它的原因。在这种情况下,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
}
    

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

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

【讨论】:

  • 您的参数和代码更改对于实现/消费者应该是什么是有意义的。虽然这并不能“解决”我的问题,但它清楚地描绘了正确的实现应该是什么。也许正确的做法是解决根本原因/问题(如您所建议的),而不是尝试修补它并长期输掉战斗。
【解决方案2】:

有点奇怪,但是……

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

【讨论】:

    猜你喜欢
    • 2016-08-20
    • 2016-03-31
    • 2010-09-20
    • 2012-03-07
    • 2012-12-21
    • 2011-10-21
    • 1970-01-01
    • 2016-03-24
    • 2015-04-17
    相关资源
    最近更新 更多