LINQ之外的产量是否有用?

当我认为我可以使用yield关键字时,我退后一步,看看它将如何影响我的项目。 我总是最终返回一个集合而不是yeilding,因为我觉得保持yeilding方法状态的开销不会给我带来太大的影响。 在几乎所有我返回集合的情况下,我觉得90%的时间,调用方法将迭代集合中的所有元素,或者将在整个集合中寻找一系列元素。

我确实理解它在linq中的用处,但我觉得只有linq团队正在编写这样复杂的可查询对象,这些对象的产生是有用的。

有没有人写过像linq这样的产品有用吗?

我最近不得不以Expression类的forms表达数学表达式。 在评估表达式时,我必须通过后序树行走来遍历树结构。 为此,我实现了IEnumerable ,如下所示:

public IEnumerator> GetEnumerator() { if (IsLeaf) { yield return this; } else { foreach (Expression expr in LeftExpression) { yield return expr; } foreach (Expression expr in RightExpression) { yield return expr; } yield return this; } } 

然后我可以简单地使用foreach来遍历表达式。 您还可以根据需要添加属性以更改遍历算法。

请注意,对于yield,您将迭代集合一次,但是当您构建列表时,您将迭代它两次。

以一个filter迭代器为例:

 IEnumerator Filter(this IEnumerator coll, Func 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团队正在编写这样复杂的可查询对象,这些对象的产生是有用的。

只要它在.NET 2.0中实现,产量就很有用,早在人们想到LINQ之前很久。

我为什么要写这个函数:

 IList LoadStuff() { var ret = new List(); foreach(var x in SomeExternalResource) ret.Add(x); return ret; } 

当我可以使用yield时,无需充分理由就可以节省创建临时列表的工作量和复杂性:

 IEnumerable LoadStuff() { foreach(var x in SomeExternalResource) yield return x; } 

它还具有巨大的性能优势。 如果您的代码恰好使用集合的前5个元素,那么使用yield通常会避免加载超过该点的任何内容。 如果您构建一个集合然后返回它,您将浪费大量的时间和空间来加载您永远不需要的东西。

我可以继续下去….

在以前的公司,我发现自己写这样的循环:

 for (DateTime date = schedule.StartDate; date <= schedule.EndDate; date = date.AddDays(1)) 

使用一个非常简单的迭代器块,我能够将其更改为:

 foreach (DateTime date in schedule.DateRange) 

它使代码更容易阅读,IMO。

为C#2开发了yield (在C#3中的Linq之前)。

在处理数据访问和大量重复计算时,我们在大型企业C#2 Web应用程序中大量使用它。

只要你有多个元素可以多次击中,集合就很棒。

但是,在许多数据访问方案中,您拥有大量元素,而这些元素不一定需要在大型集合中传递。

这基本上就是SqlDataReader所做的 – 它只是一个前向自定义枚举器。

使用它可以快速完成,并且使用最少的代码编写自己的自定义枚举器。

所有yield都可以在C#1中完成 – 它只需要大量的代码来完成它。

Linq确实最大化了屈服行为的价值,但它肯定不是唯一的应用程序。

每当你的函数返回IEnumerable时你应该使用“让步”。 不在.Net> 3.0中。

.Net 2.0示例:

  public static class FuncUtils { public delegate T Func(); public delegate T Func(A0 arg0); public delegate T Func(A0 arg0, A1 arg1); ... public static IEnumerable Filter(IEnumerable e, Func filterFunc) { foreach (T el in e) if (filterFunc(el)) yield return el; } public static IEnumerable Map(IEnumerable e, Func mapFunc) { foreach (T el in e) yield return mapFunc(el); } ... 

我不确定C#的yield()的实现,但是对于动态语言,它比创建整个集合更有效。 在许多情况下,它使得使用比RAM大得多的数据集变得容易。

我是C#的巨大收益粉丝。 在大型本地框架中尤其如此,其中通常方法或属性返回作为另一个IEnumerable的子集的List。 我看到的好处是:

  • 使用yield的方法的返回值是不可变的
  • 你只是在列表上迭代一次
  • 它是一个迟到或懒惰的执行变量,意味着返回值的代码在需要之前不会被执行(尽管如果你不知道你在做什么,这可能会咬你)
  • 源列表更改,您不必调用另一个IEnumerable,您只需再次迭代IEnumeable
  • 还有很多

产量的另一个巨大好处是,您的方法可能会返回数百万个值。 这么多,在方法甚至可以返回之前,只有构建List才有可能耗尽内存。 使用yield,该方法可以创建并返回数百万个值,并且只要调用者也不存储每个值。 因此它适用于大规模数据处理/聚合操作

个人而言,我没有发现我在正常的日常编程中使用屈服。 但是,我最近开始使用Robotics Studio示例,并发现yield在那里被广泛使用,所以我也看到它与CCR(并发和协调运行时)一起使用,在那里你有异步和并发问题。

无论如何,仍然试图让我的头围绕它。

产量很有用,因为它可以节省空间。 编程中的大多数优化都会在空间(磁盘,内存,网络)和处理之间进行权衡。 作为编程构造的Yield允许您按顺序多次迭代集合,而不需要为每次迭代单独复制集合。

考虑这个例子:

 static IEnumerable GetAllPeople() { return new List() { new Person() { Name = "George", Surname = "Bush", City = "Washington" }, new Person() { Name = "Abraham", Surname = "Lincoln", City = "Washington" }, new Person() { Name = "Joe", Surname = "Average", City = "New York" } }; } static IEnumerable GetPeopleFrom(this IEnumerable people, string where) { foreach (var person in people) { if (person.City == where) yield return person; } yield break; } static IEnumerable GetPeopleWithInitial(this IEnumerable people, string initial) { foreach (var person in people) { if (person.Name.StartsWith(initial)) yield return person; } yield break; } static void Main(string[] args) { var people = GetAllPeople(); foreach (var p in people.GetPeopleFrom("Washington")) { // do something with washingtonites } foreach (var p in people.GetPeopleWithInitial("G")) { // do something with people with initial G } foreach (var p in people.GetPeopleWithInitial("P").GetPeopleFrom("New York")) { // etc } } 

(显然,您不需要使用带有扩展方法的yield,它只是创建了一个强大的范例来考虑数据。)

正如您所看到的,如果您有很多这些“filter”方法(但它可以是任何一种方法,可以对一组人员进行操作),您可以将其中的许多方法链接在一起,而无需为每个步骤提供额外的存储空间。 这是提高编程语言(C#)以更好地表达解决方案的一种方法。

yield的第一个副作用是它会延迟执行过滤逻辑,直到你真正需要它为止。 因此,如果您创建一个IEnumerable <>类型的变量(带有yield)但从不迭代它,那么您永远不会执行逻辑或占用空间,这是一个强大而自由的优化。

另一个副作用是yield在最低公共集合接口(IEnumerable <>)上运行,这使得能够创建具有广泛适用性的类库代码。

请注意,yield允许您以“懒惰”的方式执行操作。 懒惰,我的意思是在实际请求元素之前,不会对IEnumberable中的下一个元素进行评估。 这使您可以执行几项不同的操作。 一个是你可以产生一个无限长的列表而不需要实际进行无限计算。 其次,您可以返回函数应用程序的枚举。 只有在遍历列表时才会应用这些函数。

我在非linq代码中使用了yeild这样的东西(假设函数不在同一个类中):

 public IEnumerable GetData() { foreach(String name in _someInternalDataCollection) { yield return name; } } ... public void DoSomething() { foreach(String value in GetData()) { //... Do something with value that doesn't modify _someInternalDataCollection } } 

您必须小心,不要无意中修改GetData()函数迭代的集合,否则会抛出exception。

产量通常非常有用。 在支持function样式编程的其他语言中,它是ruby,因此它与linq相关联。 反过来说,linq在样式上是function性的,所以它使用yield。

我有一个问题,我的程序在一些后台任务中使用了大量的cpu。 我真正想要的是仍然能够像正常一样编写函数,以便我可以轻松地读取它们(即整个线程与基于事件的参数)。 如果他们花了太多的CPU,仍然可以打破这些function。 产量是完美的。 我写了一篇关于此的博客文章 ,所有人都可以找到源代码:)

System.Linq IEnumerable扩展很棒,但有时你想要更多。 例如,请考虑以下扩展名:

 public static class CollectionSampling { public static IEnumerable Sample(this IEnumerable coll, int max) { var rand = new Random(); using (var enumerator = coll.GetEnumerator()); { while (enumerator.MoveNext()) { yield return enumerator.Current; int currentSample = rand.Next(max); for (int i = 1; i <= currentSample; i++) enumerator.MoveNext(); } } } } 

屈服的另一个有趣优势是调用者无法将返回值强制转换为原始集合类型并修改内部集合