如何在运行时判断IEnumerable <t>是否被延迟?</t>

时间:2010-07-06 15:20:38

标签: c# linq

我希望能够在运行时询问IEnumerable,如果它是延迟表达式,或者它是具体的集合。

因此,如果该方法被称为IsDeferred,则IsDeferred(myList.Where(i =&gt; i&gt; 5))将返回true并且IsDeferred(myList.Where(i =&gt; i&gt; 5).ToList ())将返回false。

感谢。

编辑:

我想我可以在没有提供我想做的根本原因的情况下问这个问题,但我猜不是。首先,正如其他人所指出的那样,在编译时无法告诉它。集合的类型不一定能告诉你它是否是懒惰的。您可以拥有一个IEnumerable,这是一个延迟查询而另一个不是(请参阅原始问题)。使用强力来识别具体类型并不是一个优雅的解决方案。

现在,至于我想这样做的原因。想象一个方法,它接受一个I​​Enumerable,然后多次引用它:

public void MyMethod<T>( IEnumerable<T> items) {
  foreach( var item in items )
    // Do stuff.
  Console.WriteLine( "There are " + items.Count() + " items in the collection." );
  if( items.Any() )
    // Do some more things.
}

现在,这看起来很好,但如果我调用MyMethod(myList.Where(i =&gt; i.ReallyExpensiveOperation())),那么你可以看到昂贵的Where将被执行三次。一次为迭代,一次为Count,再为Any。我可以通过让MyMethod的第一行做ToList()来解决这个问题。但是,如果我知道自己不必这样做(如果我知道它已经是一个具体的清单),那就更好了。我知道我可以重写(完全假的,而不是一个真实世界的例子)MyMethod不会多次引用items集合,但我对此不感兴趣作为解决方案。

再次感谢。

4 个答案:

答案 0 :(得分:5)

不可能为所有情况创建此方法,因为只有类的创建者知道它是否是懒惰的。示例:可以创建类,根据某些配置将值预先读入内部列表 - 您永远无法猜测。你最好的方法是创建一些众所周知的类型列表,这些类型是某些具体的集合(列表,数组等)并检查它。我认为你的工作方向错误 - 因为收集的“懒惰”应该在编译时知道或者是可配置的(不可猜测)。

修改:您可以通过调用.ToList(或.ToArray)来强制收集。调用这些方法会立即强制迭代。

答案 1 :(得分:3)

由于Eagerness取决于实现者,因此您可以做的最好的事情是根据实例的类型来决定。

    public void TypeTest()
    {
        IEnumerable<int> a = Enumerable.Empty<int>();
        IEnumerable<int> b = a.ToList();
        IEnumerable<int> c = b.Where(x => x%2 == 1);

        Console.WriteLine(a.GetType().Name);
        Console.WriteLine(b.GetType().Name);
        Console.WriteLine(c.GetType().Name);
    }

以上代码会产生以下结果:

Int32[]
List`1
WhereListIterator`1

合理地,人们会期望ArraysSystem.Collections中的类型成为集合。名称中带有Iterator字样的类型可能很懒惰。并非所有类型都如此简单......例如System.Data.Linq.EntitySet有一个属性可以告诉您它是否被延迟(并且一旦加载了EntitySet,属性就会改变)。

您不能期望创建完整的案例列表,但您可以完成足够的列表来实现目标。


我认为你正在与IEnumerable<T>契约的炫目荣耀作斗争。还有其他合同,听起来你确实需要IList<T>。如果你使用了它,那么调用者就会失职给你一个延期的集合。

答案 2 :(得分:1)

你不应该这样做,但如果你想尝试一些启发式方法,这是一种涵盖Enumerable的基本成员和微软公司C#4.0编译器中yield产生的所有枚举数的方法:

static readonly Type compilerGeneratedAttributeType = typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute);

static bool IsDefered(IEnumerable enumerable)
{
    if (enumerable == null) throw new ArgumentNullException("enumerable");

    var type = enumerable.GetType();

    var compilerGenerated = type.GetCustomAttributes(compilerGeneratedAttributeType, false).Length > 0;

    return type.IsNestedPrivate &&
        (
            type.Name.Contains("__") && compilerGenerated
            || type.DeclaringType.Equals(typeof(Enumerable))
        );
}
    }

请注意,C#规范说通常而不是必须所以即使是我的样本根据规范也未正确检测到迭代器块生成的枚举:

  

可枚举对象通常是一个   编译器生成的实例   封装了。的可枚举类   迭代器块中的代码和   实现可枚举的接口,   但其他实施方法   是可能的。如果是一个可枚举的类   由编译器生成   class应直接嵌套或嵌套   间接地,在包含的类中   功能成员,它应具有   私人无障碍,它应该   有一个为编译器使用保留的名称

答案 3 :(得分:0)

正确的答案是不可能在这里100%正确,但是适用于大多数情况的是:

public static bool IsDeferred<T>(this IEnumerable<T> source)
{
    switch (source)
    {
        case ICollection<T> collection:
            return false;

        case IReadOnlyCollection<T> readOnlyCollection:
            return false;

        default:
            return true;
    }
}

您可以为其添加更多接口,但是这个想法是,任何合理的.NET集合都至少应在上述两个接口中的任何一个上实现,但延迟查询除外。要盲目地实现您的IEnumerable,请参阅以下内容:Is there a way to Memorize or Materialize an IEnumerable?(同样,实际上应该在99%的时间内都能正常工作)