0

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

用法

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

4

5 回答 5

2

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

于 2012-08-30T04:14:11.843 回答
2

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

于 2012-08-30T04:18:48.393 回答
1

_list.where(x => x.IsGood);两者都做。话虽如此,这不是很明显哪个必须是更好的用法吗?

yield return有它的用途,但这种情况,尤其是在吸气剂中,不是一个

于 2012-08-30T04:16:17.733 回答
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);
}

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

于 2012-08-30T06:52:25.380 回答
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 时,您要做出什么保证?你是说你想简单地提供前向迭代吗?或者你是说你的界面提供了延迟执行?

在下面的代码中,我的意图是简单地提供没有随机访问的前向枚举:

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

于 2012-08-30T04:41:37.147 回答