【问题标题】:Why cannot use iterator block with IOrderedEnumerable为什么不能将迭代器块与 IOrderedEnumerable 一起使用
【发布时间】:2012-10-07 05:43:24
【问题描述】:

这是我写的:

using System;using System.Linq;
static class MyExtensions
{
    public static IEnumerable<T> Inspect<T> (this IEnumerable<T> source)
    {
        Console.WriteLine ("In Inspect");
        //return source;    //Works, but does nothing
        foreach(T item in source){
            Console.WriteLine(item);
            yield return item;
        }
    }
}

然后用这个去测试它:

var collection = Enumerable.Range(-5, 11)
    .Select(x => new { Original = x, Square = x * x })
    .Inspect()
    .OrderBy(x => x.Square)
    //.Inspect()
    .ThenBy(x => x.Original)
    ;
foreach (var element in collection)
{
Console.WriteLine(element);
}

Inspect() 的第一次使用效果很好。第二个,注释掉,不会编译。 OrderBy 的返回是IOrderedEnumerable。我原以为IOrderedEnumerable is-a IEnumerable 但是,我尝试了:

public static IOrderedEnumerable<T> Inspect<T> (this IOrderedEnumerable<T> source)
{
    Console.WriteLine ("In Inspect (ordered)");
    foreach(T item in source){
        Console.WriteLine(item);
        yield return item;
    }
}

但这也不会编译。我被告知我不能有迭代器块,因为 System.Linq.IOrderedEnumberable 不是迭代器接口类型。

我错过了什么?我不明白为什么人们不想像处理原始集合一样迭代有序集合。

(使用 Mono 2.10.8.1,它实际上是 C# 4.0,和 MonoDevelop 2.8.6.3)

更新:

正如 joshgo 好心指出的那样,我可以使用 IOrderedEnumerable 的输入参数,它确实起到了 as-a IEnumerable 的作用。但是要迭代我必须返回IEnumerable,而我原来的错误是由ThenBy引起的,它坚持要给IOrderedEnumerable。也很合理。但是这里有没有办法满足ThenBy

更新 2:

在玩过两个答案中的代码之后(这两个答案都非常有帮助),我终于明白为什么我不能使用带有 IOrderedEnumerable 返回的 yield:没有意义,因为这些值必须按顺序完全可用进行排序。因此,与其使用带有 yield 的循环,不如使用循环打印出所有项目,然后在最后只返回一次 source。

【问题讨论】:

    标签: c# .net linq mono iterator


    【解决方案1】:

    我相信可以在这里找到错误的解释:Some help understanding "yield"

    引用 Lasse V. Karlsen:

    使用 yield return 的方法必须声明为返回其中一个 以下两个接口:IEnumerable 或 IEnumerator

    问题似乎在于yield 运算符和第二个函数的返回类型IOrderedEnumerable

    如果您将返回类型从 IOrderedEnumerable 更改为 IEnumerable,则第二次 Inspect() 调用将不再是错误。但是,ThenBy() 调用现在将引发错误。如果您暂时将其注释掉,它会编译,但您确实无法访问 ThenBy() 方法。

    var collection = Enumerable.Range(-5, 11)
        .Select(x => new { Original = x, Square = x * x })
        .Inspect()
        .OrderBy(x => x.Square)
        .Inspect()
        //.ThenBy(x => x.Original)
        ;
    foreach (var element in collection)
    {
        Console.WriteLine(element);
    }
    

    ...

    public static IEnumerable<T> Inspect<T> (this IOrderedEnumerable<T> source)
    {
        Console.WriteLine ("In Inspect (ordered)");
        foreach(T item in source){
            Console.WriteLine(item);
            yield return item;
        }
    }
    

    【讨论】:

    • 这似乎根本没有回答这个问题:/
    • @Earlz 一点也不! (啊,我看到你在 joshgo 进行重大编辑之前写了你的评论。)
    • 啊哈!你是对的,问题是对 ThenBy() 的调用。事实上IEnumerable&lt;T&gt; Inspect&lt;T&gt; (this IEnumerable&lt;T&gt; source)OrderBy() 没有任何问题。但是覆盖不能仅仅通过返回类型来改变;不知道有没有办法满足ThenBy?
    • 您可以将 Inspect 函数恢复为返回 IOrderedEnumerable 类型。然后移除 yield 操作符和 for 循环,只返回一个 IOrderedEnumerable 类型。它应该可以工作,并且您应该可以访问 ThenBy()。如果您需要代码 sn-p,请告诉我。
    【解决方案2】:

    如果你想在操作后应用你的扩展方法,返回IOrdereEnumerable并继续排序,那么你需要创建第二个重载扩展:

    public static IOrderedEnumerable<T> Inspect<T>(this IOrderedEnumerable<T> source)
    {
        Console.WriteLine("In Ordered Inspect");
        // inspected items will be unordered
        Func<T, int> selector = item => { 
                  Console.WriteLine(item); 
                  return 0; };
    
        return source.CreateOrderedEnumerable(selector, null, false);    
    }
    

    这里有什么有趣的:

    • 您需要返回IOrderedEnumerable才能申请ThenByThenByDescending
    • IOrderedEnumerable 不是通过 yield return 创建的。在您的情况下,可以通过从源代码创建它来实现
    • 您应该创建虚拟选择器,它不会破坏项目的顺序
    • 输出将不包含有序项,因为选择器的执行顺序与输入序列相同。

    如果您想查看订购的商品,您需要执行您的OrderedEnumerable。这将强制执行所有出现在Inspect 之前的运算符:

    public static IOrderedEnumerable<T> Inspect<T>(this IOrderedEnumerable<T> source)
    {
        Console.WriteLine("In Ordered Inspect");            
        var enumerable = source.CreateOrderedEnumerable(x => 0, null, false);    
        // each time you apply Inspect all query until this operator will be executed
        foreach(var item in enumerable)
            Console.WriteLine(item);
        return enumerable;    
    }
    

    【讨论】:

    • 感谢 LazyB。很有意思。我认为您的第二个示例可以简化为:Console.WriteLine("In Ordered Inspect"); foreach(T item in source){Console.WriteLine("Inspect(O):"+item);} return source; (?)
    • @DarrenCook 是的,你是对的。如果我们不排序任何东西,我们可以返回源代码
    猜你喜欢
    • 2011-10-04
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多