LINQ之外的产量是否有用?

时间:2008-11-25 15:00:13

标签: c# .net linq yield

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

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

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

14 个答案:

答案 0 :(得分:27)

请注意,对于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)

你的方法是创建(和折腾)三个列表。我的方法只迭代一次。

此外,当您返回一个集合时,您正在强制对您的用户进行特定实现。迭代器更通用。

答案 1 :(得分:19)

  

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

在.NET 2.0中实现后,Yield很有用,早在人们想到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通常会避免加载超过该点的任何内容。如果你建立一个集合然后返回它,你会浪费大量的时间和空间来加载你永远不需要的东西。

我可以继续......

答案 2 :(得分:12)

我最近不得不以Expression类的形式表达数学表达式。在评估表达式时,我必须通过后序树行走来遍历树结构。为了实现这一点,我实现了IEnumerable&lt; T&gt;像这样:

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来遍历表达式。您还可以根据需要添加属性以更改遍历算法。

答案 3 :(得分:11)

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

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

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

foreach (DateTime date in schedule.DateRange)

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

答案 4 :(得分:8)

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

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

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

然而,在许多数据访问场景中,你有大量的元素,你不一定需要在一个伟大的大集合中传递。

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

yield允许您快速执行,使用最少的代码编写自己的自定义枚举器。

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

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

答案 5 :(得分:2)

每当你的函数返回IEnumerable时,你应该使用“让步”。不在.Net&gt;仅限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);
      }
        ...

答案 6 :(得分:2)

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

答案 7 :(得分:2)

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

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

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

答案 8 :(得分:1)

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

无论如何,仍然试图绕过它。

答案 9 :(得分: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类型的变量&lt;&gt; (有收益率)但从不迭代它,你永远不会执行逻辑或消耗空间,这是一个强大而自由的优化。

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

答案 10 :(得分:1)

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

答案 11 :(得分: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()函数正在迭代的集合,否则它将抛出异常。

答案 12 :(得分:0)

一般来说,收益率非常有用。在支持功能样式编程的其他语言中,它是红宝石,因此它与linq相关联。反过来说,linq在样式上是功能性的,所以它使用yield。

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

答案 13 :(得分: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();
            }
        }
    }    
}

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