这两个实现之间是否存在显着的复杂性差异,或者编译器是否仍然优化它?
用法:
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)实现?什么时候比另一个更好?
答案 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时,您不必担心延迟执行,因为大多数操作员都会为您执行此操作。在您自己的代码中,您可能希望根据您的需要选择第一个或第二个。