27

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

我确实理解它在 linq 中的用处,但我觉得只有 linq 团队在编写如此复杂的可查询对象,因此 yield 是有用的。

有没有人写过类似或不喜欢 linq 的东西,其中 yield 有用?

4

14 回答 14

28

请注意,使用 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)

您的方法将创建(并折腾)三个列表。我的方法只迭代一次。

此外,当您返回集合时,您会强制用户执行特定的实现。迭代器更通用。

于 2008-11-25T15:05:35.310 回答
19

我确实理解它在 linq 中的用处,但我觉得只有 linq 团队在编写如此复杂的可查询对象,因此 yield 是有用的。

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;
}

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

我可以继续下去....

于 2008-12-02T23:51:22.393 回答
12

我最近不得不以 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 来遍历表达式。您还可以根据需要添加一个 Property 来更改遍历算法。

于 2008-11-25T17:04:39.813 回答
11

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

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

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

foreach (DateTime date in schedule.DateRange)

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

于 2008-11-25T15:24:31.060 回答
8

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

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

每当您有一些要多次点击的元素时,集合都很棒。

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

这本质上就是这样SqlDataReader做的 - 它是一个仅向前的自定义枚举器。

让你yield做的是用最少的代码快速编写你自己的自定义枚举器。

所做的一切yield都可以在 C#1 中完成 - 只需要大量代码即可完成。

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

于 2008-11-25T16:12:53.723 回答
2

每当您的函数返回 IEnumerable 时,您都应该使用“yielding”。仅在 .Net > 3.0 中不存在。

.Net 2.0 示例:

  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);
      }
        ...
于 2008-11-25T15:21:31.520 回答
2

我不确定 C# 对 yield() 的实现,但在动态语言上,它比创建整个集合要高效得多。在许多情况下,它可以轻松处理比 RAM 大得多的数据集。

于 2008-11-25T15:46:09.160 回答
2

我是 C# 的一个巨大的产量粉丝。在大型自主开发的框架中尤其如此,其中方法或属性通常返回 List,它是另一个 IEnumerable 的子集。我看到的好处是:

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

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

于 2010-01-27T15:41:19.993 回答
1

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

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

于 2008-11-25T15:15:43.450 回答
1

产量很有用,因为它可以节省空间。编程中的大多数优化都在空间(磁盘、内存、网络)和处理之间进行权衡。Yield 作为一种编程结构,允许您按顺序对集合进行多次迭代,而无需为每次迭代创建单独的集合副本。

考虑这个例子:

static IEnumerable<Person> GetAllPeople()
{
    return new List<Person>()
    {
        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<Person> GetPeopleFrom(this IEnumerable<Person> people,  string where)
{
    foreach (var person in people)
    {
        if (person.City == where) yield return person;
    }
    yield break;
}

static IEnumerable<Person> GetPeopleWithInitial(this IEnumerable<Person> 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 与扩展方法一起使用,它只是创建了一个强大的范式来思考数据。)

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

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

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

于 2008-11-25T15:19:47.653 回答
1

请注意,yield 允许您以“懒惰”的方式做事。懒惰是指在实际请求该元素之前,不会对 IEnumberable 中的下一个元素进行评估。这使您有能力做一些不同的事情。一个是您可以生成一个无限长的列表,而无需实际进行无限计算。其次,您可以返回函数应用程序的枚举。仅当您遍历列表时才会应用这些函数。

于 2008-11-25T17:21:07.377 回答
0

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

public IEnumerable<string> 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() 函数正在迭代的集合,否则会引发异常。

于 2008-11-25T16:03:20.560 回答
0

一般来说,产量非常有用。它在支持函数式编程的其他语言中使用 ruby​​,因此它就像与 linq 相关联。相反,linq 在风格上是函数式的,所以它使用 yield。

我有一个问题,我的程序在一些后台任务中使用了大量的 cpu。我真正想要的是仍然能够像平常一样编写函数,以便我可以轻松阅读它们(即整个线程与基于事件的参数)。如果它们占用过多的 cpu,仍然能够分解这些功能。产量非常适合这一点。我写了一篇关于这个的博客文章,并且所有人都可以使用源代码:)

于 2008-12-02T23:32:33.227 回答
0

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

public static class CollectionSampling
{
    public static IEnumerable<T> Sample<T>(this IEnumerable<T> 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();
            }
        }
    }    
}

yielding 的另一个有趣的优点是调用者不能将返回值强制转换为原始集合类型并修改您的内部集合

于 2010-09-28T13:19:29.247 回答