IEnumerable的默认具体类型是什么

时间:2017-12-13 05:22:41

标签: c# linq ienumerable

(抱歉标题模糊;想不出更好的事情。请随意改写。)

因此,假设我的函数或属性返回IEnumerable<T>

public IEnumerable<Person> Adults
{
  get
  {
    return _Members.Where(i => i.Age >= 18);
  }
}

如果我在此属性上运行foreach而没有实际实现返回的可枚举:

foreach(var Adult in Adults)
{
  //...
}

是否存在一条规则来管理IEnumerable<Person>是否将实现为数组或列表或其他内容?

在不调用AdultsList<Person>的情况下将ToList()投射到ToArray()或数组也是安全的吗?

修改

许多人花了很多精力回答这个问题。感谢他们所有人。然而,这个问题的要点仍然没有答案。让我详细介绍一下:

我理解foreach不要求目标对象是数组或列表。它甚至不需要是任何类型的集合。它需要的目标对象是实现枚举。但是,如果我检查目标对象的值,它会显示实际的基础对象是List<T>(就像它在检查盒装字符串对象时显示object (string)一样)。这就是混乱开始的地方。谁进行了这种实现?我检查了底层(Where()函数的源代码),看起来这些函数看起来并不像这样。

所以我的问题在于两个层面。

  • 第一个纯粹是理论上的。与物理学和生物学等许多其他学科不同,在计算机科学中,我们总是清楚地知道某些东西是如何起作用的(回答@zzxyz的最后评论);所以我试图挖掘创建List<T>的代理以及它如何决定应该选择List而不是Array以及是否有办法从我们的代码中影响该决策。
  • 我的第二个理由是实际的。我可以依赖实际底层对象的类型并将其强制转换为List<T>吗?我需要使用一些List<T>功能,我想知道例如((List<Person>)Adults).BinarySearch()是否和Adults.ToList().BinarySearch()一样安全吗?

我也明白,即使我明确地调用ToList(),它也不会造成任何性能损失。我只是想了解它是如何工作的。无论如何,再次感谢时间;我想我花了太多时间。

4 个答案:

答案 0 :(得分:2)

一般而言,foreach工作所需要的只是让对象具有可访问的GetEnumerator()方法,该方法返回具有以下方法的对象:

void Reset()
bool MoveNext()
T Current { get; private set; } // where `T` is some type.

您甚至不需要IEnumerableIEnumerable<T>

这段代码可以在编译器找出所需的一切时使用:

void Main()
{
    foreach (var adult in new Adults())
    {
        Console.WriteLine(adult.ToString());
    }
}

public class Adult
{
    public override string ToString() => "Adult!";
}

public class Adults
{
    public class Enumerator
    {
        public Adult Current { get; private set; }
        public bool MoveNext()
        {
            if (this.Current == null)
            {
                this.Current = new Adult();
                return true;
            }
            this.Current = null;
            return false;
        }
        public void Reset() { this.Current = null; }
    }
    public Enumerator GetEnumerator() { return new Enumerator(); }
}

具有适当的可枚举性使得该过程更容易且更稳健地工作。上述代码的惯用版本更为:

public class Adults
{
    private class Enumerator : IEnumerator<Adult>
    {
        public Adult Current { get; private set; }

        object IEnumerator.Current => this.Current;

        public void Dispose() { }

        public bool MoveNext()
        {
            if (this.Current == null)
            {
                this.Current = new Adult();
                return true;
            }
            this.Current = null;
            return false;
        }

        public void Reset()
        {
            this.Current = null;
        }
    }
    public IEnumerator<Adult> GetEnumerator()
    {
        return new Enumerator();
    }
}

这使Enumerator成为私人类,即private class Enumerator。接口然后完成所有艰苦的工作 - 甚至不可能在Enumerator之外获得对Adults类的引用。

关键是你在编译时不知道类的具体类型是什么 - 如果你这样做,你甚至可能无法投射它。

界面就是你所需要的,如果你考虑我的第一个例子,即使这不是严格正确的。

如果您想要List<Adult>Adult[],则必须分别致电.ToList().ToArray()

答案 1 :(得分:1)

任何接口都没有默认具体类型这样的东西 接口的整个要点是保证属性,方法,事件或索引器,用户无需了解实现它的具体类型。

使用界面时,您可以知道的是此界面声明的属性,方法,事件和索引器,以及您实际需要知道的所有内容。这只是封装的另一个方面 - 与使用类的方法时相同,您不需要知道该方法的内部实现。

在评论中回答你的问题:

  

谁决定具体类型以防我们不这样做,就像我上面那样?

创建实现该接口的实例的代码。 既然你不能var Adults = new IEnumerable<Person> - 它必须是某种具体类型。

据我在source code中看到linq的可枚举扩展程序 - where会返回Iterator<TSource>的实例或WhereEnumerableIterator<TSource>的实例。我没有进一步检查这些类型究竟是什么,但我几乎可以保证他们都实现了IEnumerable,或者微软的人们正在使用不同的c#编译器然后我们其他人...: - )

答案 2 :(得分:1)

以下代码有希望强调为什么您和编译器都不能假设基础集合:

public class OneThroughTen : IEnumerable<int>
{
    private static int bar = 0;
    public IEnumerator<int> GetEnumerator()
    {
        while (true)
        {
            yield return ++bar;
            if (bar == 10)
                { yield break; }
        }
    }
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<int> x = new OneThroughTen();
        foreach (int i in x)
            { Console.Write("{0} ", i); }
    }
}

当然是输出:

  

1 2 3 4 5 6 7 8 9 10

注意,上面的代码在调试器中的表现非常差。我不知道为什么。这段代码表现得很好:

public IEnumerator<int> GetEnumerator()
{
  while (bar < 10)
  {
    yield return ++bar;
  }
  bar = 0;
}

(我使用static bar强调OneThroughTen不仅没有特定的集合,也没有任何集合,实际上没有任何实例数据。我们可以很容易地返回10个随机数,这可能是一个更好的例子,现在我想起来了)))

答案 3 :(得分:0)

从您编辑的问题和评论中,您听起来就像了解使用IEnumerable的一般概念,并且您不能假设“列表对象支持所有IEnumerable对象”。你真正的问题是在调试器中让你感到困惑的事情,但我们真的无法准确理解你所看到的是什么。也许屏幕截图有帮助吗?

这里我有5个IEnumerable<int>变量,我以各种方式分配,以及“Watch”窗口如何描述它们。这表明你有困惑吗?如果没有,你能构建一个类似的短程序和截图吗?

enter image description here