繁体   English   中英

IEnumerable之间的复杂性 <T> 返回类型的实现

[英]Complexity between IEnumerable<T> return type implementation

这两个实现之间是否存在明显的复杂性差异,还是编译器对其进行了优化?

用法

for(int i = 0; i < int.MaxValue; i++)
{
    foreach(var item in GoodItems)
    {
        if(DoSomethingBad(item))
           break; // this is later added.
    }
}

实施(1)

public IEnumerable<T> GoodItems
{
   get { return _list.Where(x => x.IsGood); }
}

实施(2)

public IEnumerable<T> GoodItems
{
   get { foreach(var item in _list.Where(x => x.IsGood)) yield return item; }
}

看来IEnumerable方法应始终使用(2)来实现? 什么时候比另一个更好?

我只是构建了一个示例程序,然后使用ILSpy检查了输出程序集。 第二个选项实际上将生成一个额外的类,该类将对Where的调用包装起来,但将零值添加到代码中。 代码必须遵循的额外层可能不会在大多数程序中引起性能问题,但考虑所有额外语法只是为了以稍慢的速度执行相同的操作。 在我的书中不值得。

where内部使用yield return 您无需将其包装在另一个yield return

你做_list.where(x => x.IsGood); 同时。 话虽这么说,不是很明显必须使用更好的用法吗?

yield return有其用法,但这种情况,尤其是在吸气剂中,并非如此

在“实现2”中没有有效负载的额外代码在这里的危害较小。

每次调用属性getter时,这两种变体都会导致新对象的不良创建。 因此,两个顺序的getter调用的结果将不相等:

interface IItem
{
    bool IsGood { get; set; }
}

class ItemsContainer<T>
    where T : IItem
{
    private readonly List<T> items = new List<T>();

    public IEnumerable<T> GoodItems
    {
        get { return items.Where(item => item.IsGood); }
    }

    // ...
}

// somewhere in code
class Item : IItem { /* ... */ }

var container = new ItemsContainer<Item>();
Console.WriteLine(container.GoodItems == container.GoodItems); // False; Oops!

您应该避免这种副作用:

class ItemsContainer<T>
    where T : IItem
{
    private readonly List<T> items;
    private readonly Lazy<IEnumerable<T>> goodItems;

    public ItemsContainer()
    {
        this.items = new List<T>();
        this.goodItems = new Lazy<IEnumerable<T>>(() => items.Where(item => item.IsGood));
    }

    public IEnumerable<T> GoodItems
    {
        get { return goodItems.Value; }
    }

    // ...
}

或使用方法代替属性:

public IEnumerable<T> GetGoodItems()
{
  return _list.Where(x => x.IsGood);
}

此外,如果您想将项目的快照提供给客户端代码,则该属性也不是一个好主意。

在内部,第一个版本被编译为以下形式:

public IEnumerable<T> GoodItems
{
    get
    {
        foreach (var item in _list)
            if (item.IsGood)
                yield return item;
    }
}

而第二个现在看起来像:

public IEnumerable<T> GoodItems
{
    get
    {
        foreach (var item in GoodItemsHelper)
            yield return item;
    }
}

private IEnumerable<T> GoodItemsHelper
{
    get
    {
        foreach (var item in _list)
            if (item.IsGood)
                yield return item;
    }
}

LINQ中的Where子句是通过延迟执行实现的。 因此,无需应用foreach (...) yield return ...模式。 您正在为自己以及可能在运行时进行更多工作。

我不知道第二个版本是否和第一个相同。 从语义上讲,两者不同的,因为前者执行一轮推迟执行,而后者执行两轮。 基于这些理由,我认为第二个更为复杂。

您需要问的真正问题是:当您公开IEnumerable时,您在保证什么? 您是说只想提供正向迭代吗? 还是说您的界面提供了延迟执行?

在下面的代码中,我的目的是简单地提供前向枚举,而无需随机访问:

private List<Int32> _Foo = new List<Int32>() { 1, 2, 3, 4, 5 };

public IEnumerable<Int32> Foo
{
    get
    {
        return _Foo;
    }
}

但是在这里,我想防止不必要的计算。 我希望仅在请求结果时执行昂贵的计算。

private List<Int32> _Foo = new List<Int32>() { 1, 2, 3, 4, 5 };

public IEnumerable<Int32> Foo
{
    get
    {
        foreach (var item in _Foo)
        {
            var result = DoSomethingExpensive(item);
            yield return result;
        }
    }
}

即使两个版本的Foo 外观上都相同,但它们的内部实现却执行不同的操作。 这是您需要注意的部分。 使用LINQ时,您不必担心延迟执行,因为大多数操作员都为您执行此操作。 在您自己的代码中,您可能希望根据需要选择第一个或第二个。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM