IEnumerable的默认具体类型是什么

问题描述 投票:0回答:4

(对不起,标题模糊;没想到更好。请随意改写。)

所以,假设我的函数或属性返回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()或Array也是安全的吗?

编辑

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

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

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

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

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

c# linq ienumerable
4个回答
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
投票

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

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

在评论中回答您的问题:

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

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

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


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

(我使用staticbar强调,不仅OneThroughTen没有特定的集合,它没有任何集合,事实上没有任何实例数据。我们可以很容易地返回10个随机数,这将'是一个更好的例子,现在我认为它:))


0
投票

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

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

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.