本文介绍了IEnumerable的默认具体类型是什么的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(对不起的标题,很抱歉;想不出更好的方法了.请随意改写.)

(Sorry for the vague title; couldn't think of anything better. Feel free to rephrase.)

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

So let's say my function or property returns an IEnumerable<T>:

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

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

If I run a foreach on this property without actually materializing the returned enumerable:

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

是否存在一个规则来控制IEnumerable<Person>是否将具体化为数组或列表或其他内容?

Is there a rule that governs whether IEnumerable<Person> will be materialized to array or list or something else?

在不调用ToList()ToArray()的情况下将Adults强制转换为List<Person>或数组是否安全?

Also is it safe to cast Adults to List<Person> or Array without calling ToList() or ToArray()?

许多人在回答这个问题上花费了很多心血.感谢他们所有人.但是,这个问题的要点仍然没有答案.让我详细介绍一下:

Many people have spent a lot of effort into answering this question. Thanks to all of them. However, the gist of this question still remains unanswered. Let me put in some more details:

我知道foreach不需要目标对象是数组或列表.它甚至不需要是任何种类的集合.它需要目标对象做的就是实现枚举.但是,如果我检查目标对象的值,它会发现实际的基础对象是List<T>(就像检查装箱的字符串对象时显示的是object (string)一样).这就是混乱的开始.谁执行了这个实现?我检查了底层(Where()函数的源代码),看起来这些函数似乎没有这样做.

I understand that foreach doesn't require the target object to be an array or list. It doesn't even need to be a collection of any kind. All it needs the target object to do is to implement enumeration. However if I place inspect the value of target object, it reveals that the actual underlying object is List<T> (just like it shows object (string) when you inspect a boxed string object). This is where the confusion starts. Who performed this materialization? I inspected the underlying layers (Where() function's source) and it doesn't look like those functions are doing this.

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

So my problem lies at two levels.

  • 第一个纯粹是理论上的.与物理和生物学等许多其他学科不同,在计算机科学中,我们始终精确地知道事物的工作原理(回答@zzxyz的最新评论);所以我试图研究创建List<T>的代理,以及它如何决定应该选择List而不是Array,以及是否有一种方法可以影响我们的代码.
  • 我的第二个理由是实际的.我可以依靠实际基础对象的类型并将其强制转换为List<T>吗?我需要使用某些List<T>功能,我想知道例如((List<Person>)Adults).BinarySearch()是否和Adults.ToList().BinarySearch()一样安全?
  • First one is purely theoretical. Unlike many other disciplines like physics and biology, in computer sciences we always know precisely how something works (answering @zzxyz's last comment); so I was trying to dig about the agent who created List<T> and how it decided it should choose a List and not an Array and if there is a way of influencing that decision from our code.
  • My second reason was practical. Can I rely on the type of actual underlying object and cast it to List<T>? I need to use some List<T> functionality and I was wondering if for example ((List<Person>)Adults).BinarySearch() is as safe as Adults.ToList().BinarySearch()?

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

I also understand that it isn't going to create any performance penalty even if I do call ToList() explicitly. I was just trying to understand how it is working. Anyway, thanks again for the time; I guess I have spent just too much time on it.

推荐答案

通俗地说,使foreach起作用的所有操作是使对象具有可访问的GetEnumerator()方法,该方法返回具有以下内容的对象方法:

In general terms all you need for a foreach to work is to have an object with an accessible GetEnumerator() method that returns an object that has the following methods:

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

您甚至不需要IEnumerableIEnumerable<T>.

此代码在编译器找出所有需要的内容时起作用:

This code works as the compiler figures out everything it needs:

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

具有适当的枚举数可使该过程更轻松,更可靠地运行.上面的代码的更惯用的版本是:

Having a proper enumerable makes the process work more easily and more robustly. The more idiomatic version of the above code is:

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.然后,界面将进行所有艰苦的工作-甚至不可能在Adults之外获得对Enumerator类的引用.

This enables the Enumerator to be a private class, i.e. private class Enumerator. The interface then does all of the hard work - it's not even possible to get a reference to the Enumerator class outside of Adults.

问题在于,您在编译时不知道是什么,具体的类是什么-如果您这样做的话,甚至可能无法转换为该类.

The point is that you do not know at compile-time what the concrete type of the class is - and if you did you may not even be able to cast to it.

您需要的只是接口,即使考虑我的第一个示例,也不是完全正确的.

The interface is all you need, and even that isn't strictly true if you consider my first example.

如果需要List<Adult>Adult[],则必须分别调用.ToList().ToArray().

If you want a List<Adult> or an Adult[] you must call .ToList() or .ToArray() respectively.

这篇关于IEnumerable的默认具体类型是什么的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-05 09:49