让我们逐段分解,直到你理解为止。相信我;花点时间阅读这篇文章,这将是您理解Enumerable 类型并回答您的问题的一个启示。
查看IEnumerable 接口,它是IEnumerable<T> 的基础。它包含一种方法; IEnumerator GetEnumerator();.
Enumerables 是一头棘手的野兽,因为它们可以为所欲为。真正重要的是在foreach 循环中自动调用GetEnumerator();或者您可以手动完成。
GetEnumerator() 是做什么的?它返回另一个接口IEnumerator。
这就是魔法。 IEnumerator 有 1 个属性和 2 个方法。
object Current { get; }
bool MoveNext();
void Reset();
让我们分解魔法吧。
首先让我解释一下它们通常是什么,我之所以这么说是因为就像我提到的那样,它可能是一个棘手的野兽。您可以根据自己的选择来实现它...有些类型不符合标准。
object Current { get; } 很明显。它获取IEnumerator中的当前对象;默认情况下,这可能为 null。
bool MoveNext(); 如果IEnumerator 中有另一个对象,则返回true,它应该将Current 值设置为该新对象。
void Reset(); 告诉类型从头开始。
现在让我们来实现它。请花时间查看此IEnumerator 类型,以便您理解它。意识到当您引用 IEnumerable 类型时,您甚至没有引用 IEnumerator (this);但是,您引用的类型通过 GetEnumerator() 返回此 IEnumerator
注意: 注意不要混淆名称。 IEnumerator 与 IEnumerable 不同。
IEnumerator
public class MyEnumerator : IEnumerator
{
private string First => nameof(First);
private string Second => nameof(Second);
private string Third => nameof(Third);
private int counter = 0;
public object Current { get; private set; }
public bool MoveNext()
{
if (counter > 2) return false;
counter++;
switch (counter)
{
case 1:
Current = First;
break;
case 2:
Current = Second;
break;
case 3:
Current = Third;
break;
}
return true;
}
public void Reset()
{
counter = 0;
}
}
现在,让我们创建一个IEnumerable 类型并使用这个IEnumerator。
IEnumerable
public class MyEnumerable : IEnumerable
{
public IEnumerator GetEnumerator() => new MyEnumerator();
}
这是值得一试的...当您拨打numbers.Select(n => n % 2 == 0 ? n : 0) 之类的电话时,您并没有迭代任何项目...您返回的类型与上述类型非常相似。 .Select(…) 返回IEnumerable<int>。上面看起来不错……IEnumerable 只不过是一个调用GetEnumerator() 的接口。每当您进入循环情况或可以手动完成时,都会发生这种情况。因此,考虑到这一点,您已经可以看到迭代永远不会开始,直到您调用 GetEnumerator(),即使这样它也永远不会开始,直到您调用 GetEnumerator() 的结果的 MoveNext() 方法,这是 IEnumerator 类型。
所以...
换句话说,您在通话中只引用了IEnumerable<T>,仅此而已。没有发生任何迭代。这就是代码在您的代码中跳回的原因,因为它最终确实在 ElementAt 方法中进行了迭代,然后它正在查看 Lamba 表达式。和我在一起,稍后我将更新一个示例以完整地完成本课程,但现在让我们继续我们的简单示例:
现在让我们制作一个简单的控制台应用程序来测试我们的新类型。
控制台应用
class Program
{
static void Main(string[] args)
{
var myEnumerable = new MyEnumerable();
foreach (var item in myEnumerable)
Console.WriteLine(item);
Console.ReadKey();
}
// OUTPUT
// First
// Second
// Third
}
现在让我们做同样的事情,但让它通用。我不会写那么多,但会密切关注代码的变化,你会明白的。
我将把它全部复制并粘贴到一个文件中。
整个控制台应用
using System;
using System.Collections;
using System.Collections.Generic;
namespace Question_Answer_Console_App
{
class Program
{
static void Main(string[] args)
{
var myEnumerable = new MyEnumerable<Person>();
foreach (var person in myEnumerable)
Console.WriteLine(person.Name);
Console.ReadKey();
}
// OUTPUT
// Test 0
// Test 1
// Test 2
}
public class Person
{
static int personCounter = 0;
public string Name { get; } = "Test " + personCounter++;
}
public class MyEnumerator<T> : IEnumerator<T>
{
private T First { get; set; }
private T Second { get; set; }
private T Third { get; set; }
private int counter = 0;
object IEnumerator.Current => (IEnumerator<T>)Current;
public T Current { get; private set; }
public bool MoveNext()
{
if (counter > 2) return false;
counter++;
switch (counter)
{
case 1:
First = Activator.CreateInstance<T>();
Current = First;
break;
case 2:
Second = Activator.CreateInstance<T>();
Current = Second;
break;
case 3:
Third = Activator.CreateInstance<T>();
Current = Third;
break;
}
return true;
}
public void Reset()
{
counter = 0;
First = default;
Second = default;
Third = default;
}
public void Dispose() => Reset();
}
public class MyEnumerable<T> : IEnumerable<T>
{
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<T> GetEnumerator() => new MyEnumerator<T>();
}
}
让我们回顾一下...IEnumerable<T> 是一种类型,它有一个返回IEnumerator<T> 类型的方法。 IEnumerator<T> 类型具有 T Current { get; } 属性以及 IEnumerator 方法。
让我们在代码中再分解一次并手动调出各个部分,以便您可以更清楚地看到它。这将只是应用程序的控制台部分,因为其他一切都保持不变。
控制台应用
class Program
{
static void Main(string[] args)
{
IEnumerable<Person> enumerable = new MyEnumerable<Person>();
IEnumerator<Person> enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
Console.WriteLine(enumerator.Current.Name);
Console.ReadKey();
}
// OUTPUT
// Test 0
// Test 1
// Test 2
}
仅供参考: 需要指出的是,在上面的答案中有两个版本的 Linq。 EF 或 Linq-to-SQL 中的 Linq 包含与典型 linq 不同的扩展方法。主要区别在于 Linq 中的查询表达式(当引用数据库时)将返回 IQueryable<T>,它实现了 IQueryable 接口,该接口创建了运行和迭代的 SQL 表达式。换句话说......像.Where(…) 子句这样的东西不会查询整个数据库然后对其进行迭代。它将该表达式转换为 SQL 表达式。这就是为什么像 .Equals() 这样的东西在那些特定的 Lambda 表达式中不起作用的原因。