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

时间:2012-08-30 03:58:07

标签: c# .net ienumerable

这两个实现之间是否存在显着的复杂性差异,或者编译器是否仍然优化它?

用法

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)实现?什么时候比另一个更好?

5 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:2)

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

答案 2 :(得分:1)

你们两个都做_list.where(x => x.IsGood);。说到这一点,是不是很明显哪个必须更好用?

yield return有它的用法,但这种情况,特别是在吸气剂中,不是那个

答案 3 :(得分:1)

“实现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);
}

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

答案 4 :(得分:0)

在内部,第一个版本被编译成如下所示:

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时,你有什么保证?你是说你想简单地提供前向迭代吗?或者您是否声明您的界面提供了延迟执行?

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

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时,您不必担心延迟执行,因为大多数操作员都会为您执行此操作。在您自己的代码中,您可能希望根据您的需要选择第一个或第二个。