我确实理解它在Linq中的有用性,但我觉得只有Linq团队编写这种复杂的可查询对象才会有用yield。
是否有人编写过类似或不像Linq的东西,其中yield很有用?
IEnumerator<T> Filter(this IEnumerator<T> coll, Func<T, bool> func)
{
foreach(T t in coll)
if (func(t)) yield return t;
}
MyColl.Filter(x=> x.id > 100).Filter(x => x.val < 200).Filter (etc)
我明白在linq中它的用处,但我感觉只有linq团队才会编写这样复杂的可查询对象才有用。
yield关键字在.NET 2.0中实现后就很有用了,那时还没有人想到LINQ。
我为什么要编写这个函数:
IList<string> LoadStuff() {
var ret = new List<string>();
foreach(var x in SomeExternalResource)
ret.Add(x);
return ret;
}
何时可以使用yield,而避免为无意义的原因创建临时列表所带来的工作和复杂性:
IEnumerable<string> LoadStuff() {
foreach(var x in SomeExternalResource)
yield return x;
}
使用yield关键字还可以带来巨大的性能优势。如果你的代码只需要使用集合中的前5个元素,那么使用yield通常可以避免加载超出这一点的任何内容。如果你构建了一个集合然后返回它,你将浪费大量的时间和空间去加载永远不会用到的东西。
我可以继续说下去....
我最近需要用Expression类来表示数学表达式。在计算表达式时,我必须使用后序遍历树结构。为了实现这一点,我像这样实现了IEnumerable<T>:
public IEnumerator<Expression<T>> GetEnumerator()
{
if (IsLeaf)
{
yield return this;
}
else
{
foreach (Expression<T> expr in LeftExpression)
{
yield return expr;
}
foreach (Expression<T> expr in RightExpression)
{
yield return expr;
}
yield return this;
}
}
那么我可以简单地使用 foreach 遍历表达式。您还可以添加属性以根据需要更改遍历算法。
在之前的公司,我发现自己写了如下循环:
for (DateTime date = schedule.StartDate; date <= schedule.EndDate;
date = date.AddDays(1))
通过一个非常简单的迭代器块,我能够将其更改为:
foreach (DateTime date in schedule.DateRange)
在我看来,它使得代码更易读了许多。
for
的情况比 foreach
的情况具有更丰富的上下文,尽管两个循环之间实际上没有任何真正的差别,但在感知清晰度和解释方面存在明显的差异。 我认为,代码的上下文常常是一个被忽视或宽泛认识的概念,可以对广大读者理解代码非常重要。 - jristayield
是在C#2(在C#3中使用Linq之前)开发的。
在处理数据访问和重复计算时,我们在一个大型企业C#2 Web应用程序中广泛使用它。
集合非常适合在多次访问少量元素的情况下使用。
但是,在许多数据访问场景中,您有大量元素,不一定需要在一个大集合中传递。
这本质上就是SqlDataReader
所做的——它是一个仅向前的自定义枚举器。
yield
让您快速、轻松地编写自己的自定义枚举器,代码量最小。
所有yield
所做的事情都可以在C#1中完成,只是需要大量的代码来完成。
Linq真正最大化了yield行为的价值,但它肯定不是唯一的应用。
我是C#中巨大的Yield粉丝。特别是在大型自定义框架中,常常有方法或属性返回List,该List是另一个IEnumerable的子集。我看到的好处有:
yield的另一个巨大好处是当你的方法可能会返回数百万个值时。这么多值可能会在方法返回之前耗尽内存,甚至无法构建List。使用yield,方法可以创建并返回数百万个值,只要调用者也不存储每个值。因此,它非常适合大规模数据处理/聚合操作。
public static class FuncUtils
{
public delegate T Func<T>();
public delegate T Func<A0, T>(A0 arg0);
public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1);
...
public static IEnumerable<T> Filter<T>(IEnumerable<T> e, Func<T, bool> filterFunc)
{
foreach (T el in e)
if (filterFunc(el))
yield return el;
}
public static IEnumerable<R> Map<T, R>(IEnumerable<T> e, Func<T, R> mapFunc)
{
foreach (T el in e)
yield return mapFunc(el);
}
...
个人而言,在日常编程中我还没有发现使用yield的必要。然而,最近我开始尝试使用机器人工作室的示例,并发现yield在那里被广泛使用,因此我也看到它与CCR(并发和协调运行时)一起使用,解决异步和并发问题。
无论如何,我仍在努力理解它。