C# IEnumerable<;T>;是否存储稍后调用的函数?
我最近遇到了一些代码,它们的行为与我预期的不一样C# IEnumerable<;T>;是否存储稍后调用的函数?,c#,linq,select,ienumerable,func,C#,Linq,Select,Ienumerable,Func,我最近遇到了一些代码,它们的行为与我预期的不一样 1: int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 }; 2: IEnumerable<int> result = numbers.Select(n => n % 2 == 0 ? n : 0); 3: 4: int a = result.ElementAt(0); 5: numbers[0] = 10; 6: int b = result.ElementAt(0); 我觉得我现在了解了代
1: int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };
2: IEnumerable<int> result = numbers.Select(n => n % 2 == 0 ? n : 0);
3:
4: int a = result.ElementAt(0);
5: numbers[0] = 10;
6: int b = result.ElementAt(0);
我觉得我现在了解了代码的行为(我在网上发现了其他问题,这些问题都是这种行为的结果)。但是,究竟是什么原因导致从后面的行调用
Select()
中的代码?您有一个对LINQ查询的引用,该查询的计算次数与您对它们的迭代次数相同
(您可以看到这被称为延迟执行):
如前所述,查询变量本身仅存储查询命令。查询的实际执行被延迟,直到您在foreach语句中迭代查询变量。这个概念被称为延迟执行
因为查询变量本身从不保存查询结果,所以您可以随时执行它。例如,您可能有一个数据库正在由单独的应用程序不断更新。在应用程序中,可以创建一个检索最新数据的查询,并且可以每隔一定时间重复执行该查询,每次检索不同的结果
所以,当你
IEnumerable<int> result = numbers.Select(DoSomething);
迭代结果
直到第一个元素。对于ElementAt(4)
,也会发生同样的情况,但这次它会迭代到第五个元素。请注意,您只看到打印的正在执行某些操作。。。5
因为当前的只计算一次。
如果此时查询无法生成5项,则调用将失败。调用
.Count
再次迭代结果
查询,并返回此时的元素数量
如果您没有保留对查询的引用,而是保留了对结果的引用,即:
IEnumerable<int> result = numbers.Select(DoSomething).ToArray();
// or
IEnumerable<int> result = numbers.Select(DoSomething).ToList();
让我们一块一块地把它分解,直到你理解为止。相信我;花点时间阅读这篇文章,它将对您理解可枚举类型和回答您的问题有所启示。 查看
IEnumerable
界面,它是IEnumerable
的基础。它包含一种方法<代码>IEnumerator GetEnumerator()代码>
枚举数是一个棘手的问题,因为它们可以做任何想做的事情。真正重要的是在foreach
循环中自动调用GetEnumerator()
;或者你可以手工做
GetEnumerator()
做什么?它返回另一个接口,IEnumerator
这就是魔法。IEnumerator
有1个属性和2个方法
object Current { get; }
bool MoveNext();
void Reset();
让我们来打破魔法
首先让我解释一下它们的典型特征,我之所以说典型特征,是因为正如我所提到的,它可能是一种狡猾的野兽。无论您选择什么方式,您都可以实现此功能。。。有些类型不符合标准
对象当前{get;}
很明显。它获取IEnumerator
中的当前对象;默认情况下,这可能为空
bool MoveNext()
如果IEnumerator
中有另一个对象,则返回true
,并应将当前值设置为该新对象
void Reset()代码>告诉类型从头开始
现在让我们实现这个。请花时间查看此IEnumerator
类型,以便您理解它。要意识到,当您引用一个IEnumerable
类型时,您甚至没有引用IEnumerator
(这一点);但是,您正在引用一个类型,该类型通过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)
的电话时,你没有迭代任何项目。。。您返回的类型与上面的类型非常相似<代码>选择(…)
返回IEnumerable
。好吧,看看上面IEnumerable
只是一个调用GetEnumerator()
的接口。每当您进入循环情况或可以手动完成时,就会发生这种情况。因此,考虑到这一点,您已经可以看到迭代在调用GetEnumerator()
之前从未开始,甚至在调用GetEnumerator()
的结果的MoveNext()
方法之前也从未开始,该方法是IEnumerator
类型
所以
换句话说,您只需要在调用中引用一个IEnumerable
,就可以了。没有发生迭代。这就是代码在您的代码中跳回的原因,因为它最终在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>();
}
}
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
}
现在,让我们做同样的事情,但使其通用。我不会写那么多,但密切监视代码的变化,你会得到它
我将把它全部复制粘贴到一个文件夹中
整个控制台应用程序
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>();
}
}
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或LINQtoSQL包含与典型Linq不同的扩展方法。主要区别在于Linq中的查询表达式(在引用数据库时)将返回IQueryable
,它实现了IQueryable
接口,该接口创建运行和迭代的SQL表达式。换句话说。。。类似于.W的东西