C# 中如何实现? - 第 3 部分 (C# LINQ 详解)






4.95/5 (179投票s)
语言集成查询 (LINQ) 是 Microsoft .NET Framework 的一个组件,它充当对象和数据之间的通信器。本文部分摘自《Expert C# 5.0》一书,它将使用 C# 和 IL 代码探讨 LINQ 中使用的各种扩展方法。
目录
- LINQ 基础
- 扩展方法
- Where 和 Select
- 全部
- 任意
- Average
- Concat
- Contains
- Count
- DefaultIfEmpty
- Distinct
- ElementAt
- Empty
- Except
- First
- FirstOrDefault
- Union
- Intersect
- Last
- LastOrDefault
- LongCount
- 最大值
- 最小值
- OfType
- Range
- Repeat
- Reverse
- Single
- Skip
- SkipWhile
- Sum
- ThenBy
- ToArray
- ToDictionary
- ToList
- Zip
- 相关文章
- 历史
LINQ 基础
在 .NET 中,任何派生自 mscorlib.dll 程序集中 System.Collections.Generic
命名空间下的 IEnumerable<T>
接口的数据结构(该程序集位于 C:\Windows\Microsoft.NET\Frameworkv4.0.30319,但取决于 VS 的安装)都可以访问 System.Core.dll 程序集 System.Linq
命名空间下的 Enumerable
类中定义的所有扩展方法(更多关于 LINQ)。此 Enumerable
类是定义在 System.Core.dll 程序集(C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll,但取决于 VS 的安装)System.Linq
命名空间下的一个静态非继承类。Enumerbale
类的定义如下:
.class public abstract auto ansi sealed beforefieldinit System.Linq.Enumerable extends [mscorlib]System.Object
注意:下图使用 ILDasm.exe 程序反编译了 Enumerable
类。
静态 Enumerable
类是 IEnumerable<T>
接口的各种扩展方法的容器,例如:
public static bool Contains<TSource>(
this IEnumerable<TSource> source, TSource value)
{ /* code removed*/}
public static int Count<TSource>(
this IEnumerable<TSource> source)
{ /* code removed*/}
public static IEnumerable<TSource> Distinct<TSource>(
this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
{ /* code removed*/}
// and many more
请在此处 阅读更多关于扩展方法的信息 或 Expert C# 5.0 with .NET Framework 4.5 书籍 的第 4 章。
扩展方法 - 幕后 - 现在让我们做一些有趣的工作,在此研究中,我使用 ILDasm(C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\ildasm.exe,但这适用于我已安装的 VS)工具,该工具随 VS 一起安装。我使用的 C# 代码与 OP 提供的相同,只是将类名
Exte
更改为ExtensionMethodClass
。我构建了程序,并将程序的 exe 文件(在本例中为ConsoleApplication24.exe
,因为我将代码放在名为 ConsoleApplication24 的控制台应用程序中)放入 ILDasm 程序中,ILDasm 会为ExtensionMethodClass
类生成以下 IL 代码,其中我们定义了扩展方法。.class public abstract auto ansi sealed beforefieldinit ExtensionMethodClass extends [mscorlib]System.Object { .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() .method public hidebysig static string GetFirstThreeCh(string str) cil managed { .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() .maxstack 3 .locals init ( [0] string str2, [1] bool flag) //removed all the code for clearity } }如果我们查看上面的 IL 代码,我们可以看到
ExtensionMethodClass
类被定义为抽象和密封。- 在此类内部,
GetFirstThreeCh
方法被编译为静态公共方法,该方法具有字符串类型的参数。请注意,这里没有我们在 C# 代码中为GetFirstThreeCh
方法定义的 this 关键字。- 因此,从这个 IL 代码可以清楚地看出,扩展方法被定义为与静态方法相同。
让我们看看这个
GetFirstThreeCh
扩展方法在 Main 方法中的用法,这将向我们展示扩展方法在幕后的调用约定。我使用 ILdasm 从ConsoleApplication24.exe
程序的程序类中提取了以下 IL 代码。.method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 1 .locals init ( [0] string str) L_0000: nop L_0001: ldstr "my new String" L_0006: stloc.0 L_0007: ldloc.0 L_0008: call string ConsoleApplication24.ExtensionMethodClass::GetFirstThreeCh(string) L_000d: stloc.0 L_000e: ret }从上面的 L_0008 标签处的 IL 代码可以看出,
GetFirstThreeCh
方法被调用为与 CLR 调用静态方法相同。为了测试这一点,我创建了一个带有静态方法的简单静态类,如下所示:namespace ConsoleApplication24 { class Program { static void Main(string[] args) { TestStaticMethod.TestMethod(); } } public class TestStaticMethod { public static void TestMethod() { /*Code has been removed*/ } } }构建新程序后,我使用 ILDasm 程序对其进行反编译以生成以下 IL 代码:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 8 L_0000: nop L_0001: call void ConsoleApplication24.TestStaticMethod::TestMethod() L_0006: nop L_0007: ret }因此,我们可以看到 CLR 在幕后是如何处理扩展方法的。
让我们看看这些扩展方法是如何在内部工作的。
扩展方法
Where 和 Select
Where 和 Select 是 IEnumerable<TSource>
接口的两个重要扩展方法,它们定义在 System.Core.dll 程序集的 Enumerable
类中。
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
因此,任何派生自 IEnumerable<TSource>
接口的数据结构都可以对其进行访问,例如 List<T>
类。List<T> 类实现了 IEnumerable<T>
接口,如 List<T>
类的签名所示,如下所示:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
有关更多信息,请参阅 Where 和 Select。让我们看一个在此处使用 Where 和 Select 扩展方法的示例。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> numbers = new List<string>()
{
"One", "Two", "Three", "Four",
"Five", "Six", "Seven"
};
var numbersLengthThree =
numbers.Where(x => x.Length == 3).Select(x => x).ToList();
numbersLengthThree.ForEach(x => Console.WriteLine(x));
}
}
}
此代码将创建一个字符串对象列表,并将其存储在 List<string>
对象 numbers 中。上述程序将找出 numbers 中字符总数等于 3 的项,并将结果存储在新列表 numbersLengthThree 中。最后,在控制台上显示数字。此程序将产生以下输出:
One
Two
Six
让我们做一些研究来找出它是如何工作的。上面示例中最重要的一段代码是 numbers.Where(x => x.Length == 3).Select(x => x).ToList()
。下图将解释此执行的整个过程。
从上图可以看出,CLR 将 numbers 列表作为输入传递给 Where 方法,以及 MulticastDelegate
实例,该实例包含有关 <Main>b_1
方法(由匿名方法 (x=>x.Length == 3)
创建)的信息。从 Where 方法返回 WhereListIterator<string>
迭代器的实例,然后将该实例用作 Select
子句的输入参数,并带有另一个 MulticastDelegate
类的实例,该实例将包含有关方法 <Main>b_2
(由匿名方法 (x=>x)
创建)的信息。Select
方法将根据输入实例化相关的迭代器。在这种情况下,它将是 WhereSelectListIterator<string,string>
迭代器。这将作为输入参数传递给 ToList()
方法,该方法将最终通过迭代原始列表来获取基于过滤条件的列表。执行的详细信息如下:
步骤 1:在编译时,编译器将使用 Where 方法提供的匿名方法,即 x => x.Length == 3
,创建一个方法 <Main>b_1
。
注意:我使用 ILDasm.exe(C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\ILDasm.exe,具体取决于 VS 的安装)程序反编译了上述程序生成的可执行文件,并查找了 x => x.Length == 3
代码生成的 <Main>b_1
方法,如下所示:
.method private hidebysig static bool <Main>b__1(string x) cil managed
{
.maxstack 2
.locals init (
[0] bool CS$1$0000)
L_0000: ldarg.0
L_0001: callvirt instance int32 [mscorlib]System.String::get_Length()
L_0006: ldc.i4.3
L_0007: ceq
L_0009: stloc.0
L_000a: br.s L_000c
L_000c: ldloc.0
L_000d: ret
}
或等效的 C# 代码如下:
private static bool <Main>b__1(string x)
{
return (x.Length == 3);
}
CLR 将使用方法 <Main>b_1
创建 MulticastDelegate
类的实例,并将此方法信息存储在 MulticastDelegate
实例中,然后继续执行。
步骤 2:CLR 将继续执行,并使用原始列表 numbers
和 MulticastDelegate
实例(在步骤 1 中创建)作为输入,进入 Enumerable
类的 Where<TSource>(this IEnumerable<TSource> source, Func<tsource,> predicate)
方法。根据 numbers
对象的源类型,Where 方法将返回适当的迭代器实例作为输出。例如,根据上面的示例代码,它将返回 WhereListIterator<TSource>
迭代器,该迭代器将包含原始列表作为源,并将 <Main> b_1
作为谓词。
注意:迭代器类的完整列表可以在 System.Core.dll 程序集中找到,如下所示:
CLR 将使用 WhereListIterator<TSource>
作为参数传递给 Select
子句。
步骤 3:在编译时,编译器还使用匿名方法代码 (x=>x)
创建了另一个方法 <Main>b_2
,
注意:使用 ILDasm.exe 程序从上述示例程序的执行文件中提取的 <Main>b_2
方法的内容,
.method private hidebysig static string <Main>b__2(string x) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
.maxstack 1
.locals init (
[0] string CS$1$0000)
L_0000: ldarg.0
L_0001: stloc.0
L_0002: br.s L_0004
L_0004: ldloc.0
L_0005: ret
}
或等效的 C# 代码,
private static string <Main>b__2(string x)
{
return x;
}
CLR 将使用此作为输入参数传递给 MulticastDelegate
类,并创建另一个 MulticastDelegate
类型的实例。然后,它将使用从步骤 2 实例化的迭代器和此步骤中的委托实例作为 Select
子句的输入,并将执行移至 Select 方法。
步骤 4:在 Enumerable 类的 Select 方法中,CLR 将根据 Enumerable
对象和选择器委托的输入实例化相关的迭代器。对于上述示例,它将返回 WhereSelectListIterator<tsource,tresult>
迭代器的实例。此迭代器将包含原始列表、包含匿名方法的谓词委托 <Main>b_1
和包含匿名方法的选择器委托
步骤 5:CLR 将此 WhereSelectListIterator<string,string>
迭代器实例作为输入参数传递给 ToList() 方法。在 ToList() 方法中,CLR 将通过将 WhereSelectListIterator<string,string>
迭代器作为输入传递给它来创建 List<TSource> 类型的实例。
步骤 6:在 List<TSource> 类的构造函数中,CLR 将把输入的列表复制到一个新列表中,并通过迭代器 (WhereSelectListIterator<string,string>
) 的枚举器遍历这个新列表。这将确保原始列表不被更改,并将结果添加到 List 对象中的动态数组 _items
中。这个列表对象将作为基于过滤条件的原始列表结果返回。近似代码如下:
public List(IEnumerable<T> collection)
{
ICollection<T> is2 = collection as ICollection<T>;
if (is2 != null)
{
int count = is2.Count;
this._items = new T[count];
is2.CopyTo(this._items, 0);
this._size = count;
}
else
{
this._size = 0;
this._items = new T[4];
using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
this.Add(enumerator.Current);
}
}
}
}
All
此扩展方法确定序列中的所有元素是否都满足条件,如果源序列中的每个元素都通过指定的谓词条件,或者序列为空,则返回 true;否则返回 false。阅读 更多
此扩展方法的签名如下:
public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
此扩展方法用于确定项目序列是否满足条件,即序列中的每个项目都将根据谓词进行评估。在以下程序中,我创建了一个 List<string>
numbers 的实例,其中包含 One、Two、Three 等项。以下程序将找出该序列中的项目是否至少有三个字符。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> numbers = new List<string>()
{
"One", "Two", "Three", "Four",
"Five", "Six", "Seven"
};
if (numbers.All<string>(x => x.Length >= 3))
Console.WriteLine("All numbers have at least three characters.");
}
}
}
上述程序将产生以下输出:
All numbers have at least three characters.
因为扩展方法 All 将匹配谓词中指定的条件,无论它是否对序列中的项目有效。因此,它将按以下方式工作:
从上图可以看出,CLR 将 numbers 列表作为输入传递给扩展方法 All,以及使用匿名方法 (x=>x.Length >= 3)
代码创建的 MulticastDelegate
类的实例。在 All 方法中,CLR 将处理列表以确定列表中的每个项目是否都满足通过提供的谓词指定的条件。
CLR 将按如下方式执行扩展方法 All:
步骤 1:编译器将使用匿名方法 (x => x.Length >= 3)
构造一个方法 <Main>b_1
。CLR 将此 <Main>b_1
方法传递给 MulticastDelegate
类以实例化它。CLR 将原始列表和此步骤中创建的 MulticastDelegate
类实例作为输入传递给 Enumerable
类的扩展方法 All。
步骤 2:All
扩展方法将遍历列表,并尝试找出序列中是否有任何元素不满足条件,并返回 false,否则返回 true 作为操作结果。以下是此逻辑的近似代码:
public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (TSource local in source)
{
if (!predicate(local))
{
return false;
}
}
return true;
}
C# 方法源自以下 IL 代码,该代码由 ILDasm.exe 程序为 System.Core.dll 程序集的 Enumerable
类生成:
.method public hidebysig static bool All<TSource>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> source,
class [mscorlib]System.Func`2<!!TSource, bool> predicate) cil managed
{
.custom instance void System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
.maxstack 2
.locals init (
[0] !!TSource local,
[1] bool flag,
[2] class [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource> enumerator)
L_001c: ldarg.0
L_001d: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0>
[mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource>::GetEnumerator()
L_0022: stloc.2
L_0023: br.s L_0039
L_0025: ldloc.2
L_0026: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource>::get_Current()
L_002b: stloc.0
L_002c: ldarg.1
L_002d: ldloc.0
L_002e: callvirt instance !1 [mscorlib]System.Func`2<!!TSource, bool>::Invoke(!0)
L_0033: brtrue.s L_0039
L_0035: ldc.i4.0
L_0036: stloc.1
L_0037: leave.s L_004f
L_0039: ldloc.2
L_003a: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_003f: brtrue.s L_0025
L_0041: leave.s L_004d
L_0043: ldloc.2
L_0044: brfalse.s L_004c
L_0046: ldloc.2
L_0047: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_004c: endfinally
L_004d: ldc.i4.1
L_004e: ret
L_004f: ldloc.1
L_0050: ret
.try L_0023 to L_0043 finally handler L_0043 to L_004d
}
Any
此扩展方法确定序列中的任何元素是否存在或满足作为谓词提供的条件。阅读 更多。
此扩展方法的签名如下:
public static bool Any<TSource>(this IEnumerable<TSource> source)
public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
上述两个扩展方法将执行以下操作:
Any
扩展方法的第一种版本将找出项目序列中是否存在任何元素。Any
扩展方法的第二种版本将找出序列中是否有任何元素满足提供的谓词条件。
我编写了一个小程序来解释这两种版本的 Enumerable
类扩展方法的工作细节。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> numbers = new List<string>()
{
"One", "Two", "Three", "Four",
"Five", "Six", "Seven"
};
if (numbers.Any<string>())
Console.WriteLine("The sequence contains item.");
if (numbers.Any<string>(x => x.Length >= 3))
Console.WriteLine("The sequence contains at least a item which has three or more characters");
}
}
}
上述程序将产生以下输出:
The sequence contains item.
The sequence contains at least a item which has three or more characters
当 CLR 找到 Any
扩展方法的第一种版本时,它将执行以下步骤来执行操作:
步骤 1:CLR 将将原始序列(在本例中为 numbers)作为输入发送到 Any<TSource>(this IEnumerable<TSource> source)
扩展方法。
此方法将通过 numbers 列表返回的 Enumerator
对象遍历列表 numbers,并检查调用其 MoveNext()
方法时枚举器是否返回 true 值,否则返回 false,即序列中没有元素。
Any
扩展方法的近似代码如下:
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
return true;
}
}
return false;
}
对于 Any
扩展方法的第二种版本,CLR 将执行以下步骤来执行:
步骤 1:编译器将使用匿名方法 (x => x.Length >= 3)
构造一个方法 <Main>b_1
。CLR 将此 <Main>b_1
方法传递给 MulticastDelegate
类以实例化它,并将此 MulticastDelegate
实例作为谓词以及原始列表传递给 Any 扩展方法。
步骤 2:在 Any 扩展方法内部,CLR 将遍历列表以使用每个项目作为谓词的输入来执行谓词。如果谓词在第一次迭代中返回 true,则返回 true,否则将继续进行,直到找到匹配项或未找到。
public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (TSource local in source)
{
if (predicate(local))
{
return true;
}
}
return false;
}
Average
Average
扩展方法计算数值序列的平均值。阅读 更多。
这些扩展方法的签名如下:
public static double Average(this IEnumerable<int> source)
public static decimal Average<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector)
Average
扩展方法的示例如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int>()
{
1,2,3,4,5,6,7,8,9,10
};
Console.WriteLine("Average of the numbers :{0}", numbers.Average());
Console.WriteLine("Average of the original numbers x2 :{0}",
numbers.Average((x => x * 2)));
}
}
}
上述程序将产生以下输出:
Average of the numbers :5.5
Average of the original numbers x2 :11
因此,当 CLR 在上述程序中找到 Average
方法的第一种版本时,它将执行以下步骤来执行操作:
步骤 1:CLR 将原始列表(在本例中为 numbers)作为输入传递给 Average
方法。
步骤 2:Average 方法将遍历列表并对给定列表执行平均值操作。近似代码如下:
public static double Average(this IEnumerable<int> source)
{
long num = 0L;
long num2 = 0L;
foreach (int num3 in source)
{
num += num3;
num2 += 1L;
}
return (((double) num) / ((double) num2));
}
从上图可以看出,CLR 将 numbers 列表作为输入传递给 Average 方法。此方法将通过迭代原始列表来处理它,并计算 numbers 列表中存储的项目的平均值,然后将项目的平均值作为结果返回。
Average 扩展方法的第二种版本将执行以下步骤:
步骤 1:CLR 将原始列表以及使用 <Main>b_1
方法(由编译器使用匿名方法 (x=>x*2)
在编译时生成)创建的 MulticastDelegate
实例作为输入传递给 Average 扩展方法。
步骤 2:CLR 将调用 Select
方法(public static IEnumerable<TResult> Select<tsource,>(this IEnumerable<TSource> source, Func<tsource,> selector)
),它将从此方法实例化 WhereSelectListIterator<int,>
迭代器,然后将其返回给 Select
方法。
步骤 3:CLR 然后将调用 Average
方法,该方法将接受迭代器实例,例如,在步骤 1 中创建的 WhereSelectListIterator<int,>
。此迭代器将包含原始列表和 MulticastDelegate
实例作为选择器。
步骤 4:在 Average
方法中,ForEach
语句将遍历列表
foreach (int num3 in source)
{
num += num3;
num2 += 1L;
}
并执行平均值计算。下图显示:
从上图可以看出,CLR 将 numbers 列表作为输入传递给 Average 扩展方法(Average<int>(this IEnumerable<int> source, Func<int,> selector)
)以及由匿名方法 (x=>x*2)
代码创建的 MulticaseDelegate
类的实例。CLR 将此传递给 Select
方法,从该方法实例化适当的迭代器并返回给 Average 方法。在这种情况下,它将是 WhereSelectListIterator<int,int>
迭代器,它将被传递给 Average 方法来处理原始列表。WhereSelectListIterator<int,int>
迭代器将包含原始列表和内部的选择器。从平均值方法开始,CLR 将遍历列表并根据选择器中提供的过滤条件计算平均值。
Concat
此扩展方法将两个序列连接起来。此方法通过使用延迟执行来实现(请在此处 阅读更多关于延迟执行的信息)。直接返回值是相关迭代器类型的一个实例,该实例存储了执行操作所需的所有信息。直到通过直接调用其 GetEnumerator 方法或在 C# 中使用 foreach 语句枚举对象后,才执行此方法表示的查询。阅读 更多。
Concat<TSource>(IEnumerable<TSource>, IEnumerable<TSource>)
方法与 Union 方法不同,因为 Concat<TSource>(IEnumerable<TSource>, IEnumerable<TSource>)
方法返回输入序列中的所有原始元素。Union 方法仅返回序列中的唯一元素。此扩展方法的签名如下:
public static IEnumerable<TSource> Concat<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
以下程序将展示 Concat
方法的用法:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> listOne = new List<int>()
{
1,2,3,4,5
};
IList<int> listTwo = new List<int>()
{
6,7,8,9,10
};
var result = listOne.Concat(listTwo).ToList();
result.ForEach(x=> Console.WriteLine(x));
}
}
}
上述程序将产生以下输出:
1
2
3
4
5
6
7
8
9
10
Concat
扩展的工作原理如下:
从上图可以看出,CLR 将 listOne
和 listTwo
作为输入传递给 Concat
方法,从 Concat
方法中,它将返回 ConcatIterator<int>
实例作为输出给该扩展方法的调用者。尽管这将使用延迟执行模式执行,但 ToList()
方法将开始使用 listOne
和 listTwo
中 ConcatIterator
类实现的逻辑来生成最终列表。
当 CLR 找到 Concat
扩展方法时,它将执行以下步骤来执行操作:
步骤 1:CLR 将原始列表(在本例中为 listOne
和 listTwo
)作为输入参数传递给 Concat<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
方法。
步骤 2:从 Concat 方法开始,CLR 将返回 ConcatIterator<int>
迭代器的实例,该迭代器将包含 listOne
和 listTwo
,并返回给 Concat 方法的调用者。Concat 方法的近似代码如下:
public static IEnumerable<TSource> Concat<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
{
return ConcatIterator<TSource>(first, second);
}
步骤 3:由于延迟执行,此迭代器将由 ToList()
方法执行,该方法将通过 ConcatIterator<int>
实例返回的 Enumerator
对象遍历列表,如 listOne
和 listTwo
,并将 listOne
和 listTwo
中的每个项目插入到一个新列表中并作为结果返回。
Contains
Contains
扩展方法确定序列是否包含指定元素。阅读 更多
此方法的工作原理如下:
- 它通过使用默认相等比较器来确定序列是否包含指定元素。
- 它通过使用指定的
IEqualityComparer<T>
来确定序列是否包含指定元素。
此扩展方法的签名如下:
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value)
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer)
此方法将搜索列表,看它是否包含某个值。为了解释此方法,我编写了一个小程序
using System;
using System.Collections;
using System.Collections.Generic;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> listOne = new List<int>()
{
1,2,3,4,5
};
var resultAsTrue = listOne.Contains(2);
var resultAsFalse = listOne.Contains(200);
Console.WriteLine("{0}\n{1}", resultAsTrue, resultAsFalse);
}
}
}
上述程序将产生以下输出:
True
False
因此,当编译器在上述程序中找到 Contains 方法的第一种版本时,它将执行以下步骤来执行操作:
- CLR 将在 Contains 方法中搜索列表中的特定项。此搜索将有两个方向:如果输入为 null 值,则它将遍历列表以将项与 null 匹配,如果列表中的某个项为 null,则返回 true,否则返回 false。除了 null 值,CLR 将把值(作为输入提供进行匹配)与列表中的每个项进行比较,并根据匹配返回布尔结果。
Contains 方法的近似代码如下:
public bool Contains(T item)
{
if (item == null)
{
for (int j = 0; j < this._size; j++)
{
if (this._items[j] == null)
{
return true;
}
}
return false;
}
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
for (int i = 0; i < this._size; i++)
{
if (comparer.Equals(this._items[i], item))
{
return true;
}
}
return false;
}
Count
此 count 扩展方法返回序列中的元素数。阅读 更多。
此扩展方法的签名如下:
public static int Count<TSource>(this IEnumerable<TSource> source)
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
上述两个扩展方法将执行以下操作:
- 第一种版本返回序列中的元素数。
- 第二种版本返回一个数字,表示指定序列中有多少个元素满足某个条件。
Count
方法将找出列表中有多少项。例如,在以下程序中,我创建了一个字符串对象列表。我使用 Count()
方法来找出列表中有多少项,以及有多少项的长度大于三个字符。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> listOne = new List<string>()
{
"One","Two","Three"
};
var result = listOne.Count();
var fourOrMoreCharacters = listOne.Count(item => item.Length > 3);
Console.WriteLine("{0}\n{1}", result,fourOrMoreCharacters);
}
}
}
上述程序将产生以下输出:
3
1
在此示例中,我使用了 Count 方法的两种不同版本:
步骤 1:当 CLR 找到 Count 方法的第一种版本时,它将尝试找到给定列表的 Enumerator 对象,并遍历项(使用列表的迭代器),直到枚举器的 MoveNext()
方法返回 false。
此方法将返回迭代次数作为此程序的输出,因为迭代次数将是列表中的项数。Count 的近似代码如下:
public static int Count<TSource>(this IEnumerable<TSource> source)
{
int num = 0;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{num++;}
}
return num;
}
步骤 2:Count() 方法的第二种版本将接受原始列表和一个谓词,以便根据条件过滤计数。谓词将在编译时根据匿名方法创建。CLR 将遍历列表项,并在遍历过程中对每个项执行谓词。如果在迭代中谓词满足该项的条件,则会增加项计数。最后,它将返回项计数作为满足条件的项总数。Count 方法的近似代码如下:
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (TSource local in source)
{
if (predicate(local))
{
num++;
}
}
return num;
}
从上图可以看出,CLR 将 numbers 列表作为输入传递给 Count 方法,以及使用 <Main>b_1
创建的 MulticastDelegate
类的实例,该实例将使用匿名方法 (item=>item.Length > 3)
代码创建。在 count 方法中,CLR 将遍历每个项并对该项执行委托对象,根据委托的返回值,计数将增加,并返回结果作为列表的总计数,这取决于 (item=>item.Length > 3)
提供的条件。
DefaultIfEmpty
此扩展方法返回 IEnumerable<T>
的元素,如果序列为空,则返回一个默认值的单例集合。阅读 更多
此扩展方法的签名如下:
public static IEnumerable<TSource> DefaultIfEmpty<TSource>(this IEnumerable<TSource> source)
public static IEnumerable<TSource> DefaultIfEmpty<TSource>(this IEnumerable<TSource> source, TSource defaultValue)
上述两个扩展方法将执行以下操作:
- 第一种版本返回指定序列的元素,或者在序列为空时返回类型参数的默认值组成的单例集合。
- 第二种版本返回指定序列的元素,或者在序列为空时返回指定的单例集合。
此方法可用于没有项的列表,如果我们在此列表上调用扩展方法,它将返回项的默认值。让我们看看使用 DefaultIfEmpty
方法的以下程序:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<Person> persons = new List<Person>();
IList<int> numbers = new List<int>();
IList<string> names = new List<string>();
var defaultPersons = persons.DefaultIfEmpty();
var defaultNumbers = numbers.DefaultIfEmpty().ToList();
var defaultNames = names.DefaultIfEmpty();
}
}
class Person
{
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
public int Age
{
get;
set;
}
}
}
在上述程序中,我声明了三个 person 对象、numbers 和 names 类型的列表,分别为 Person、int 和 string。这三个列表都没有项,并且此列表的 Count 属性将返回 0。当我在此类列表上调用 DefaultIfEmpty
扩展方法时,CLR 将执行以下步骤来处理它:
- CLR 将列表复制到此
DefaultIfEmpty
方法中,从此方法开始,CLR 将返回DefaultIfEmptyIterator<TSource>
迭代器的实例,该迭代器将包含defaultvalue
和源值。defaultvalue
属性将包含列表类型的默认值,而 source 将是原始列表。 - CLR 将
DefaultIfEmptyItereator
传递给ToList()
方法,该方法将调用 List 类,并将DefaultIfEmptyItereator
的对象作为输入传递。在此类中,CLR 将遍历原始列表并处理结果。
DefaultIfEmptyIterator
的近似代码如下:
private static IEnumerable<TSource> DefaultIfEmptyIterator<TSource>(IEnumerable<TSource> source, TSource defaultValue)
{
using (IEnumerator<TSource> iteratorVariable0 = source.GetEnumerator())
{
if (iteratorVariable0.MoveNext())
do
{
yield return iteratorVariable0.Current;
}
while (iteratorVariable0.MoveNext());
else
yield return defaultValue;
}
}
Distinct
此扩展方法返回序列中的唯一元素。阅读 更多。
此扩展方法的签名如下:
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source)
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
上述两个扩展方法将执行以下操作:
- 第一种版本通过使用默认相等比较器比较值来返回序列中的唯一元素。
- 第二种版本通过使用指定的
IEqualityComparer<T>
比较值来返回序列中的唯一元素。
Distinct
扩展方法将返回列表中的相同项,即如果我们有一个包含重复项的列表,使用此方法,它将过滤掉重复项并返回一个新列表,该列表仅包含每个项一次。让我们看下面的程序,我在此程序中使用 Distinct 方法处理一个包含 {1,1,1,2,2,2,3,3,3} 的列表。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int>()
{
1,1,1,2,2,2,3,3,3
};
var distinctedNumbers = numbers.Distinct().ToList();
distinctedNumbers.ForEach(x=>Console.WriteLine(x));
}
}
}
此程序将产生以下输出:
1
2
3
当上述程序运行时,它将产生 {1, 2, 3} 作为输出。下图显示了 distinct 方法的工作原理:
要执行 Distinct 方法,CLR 将执行以下操作:
步骤 1:CLR 将原始列表复制到 Distinct 方法中作为输入,该方法将内部调用 Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
方法,它将返回 DistinctIterator<TSource>( IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
类的实例。但由于延迟执行,此迭代器不会执行(要执行 DistinctIterator
迭代器,我们需要调用列表上的 ToList()
方法或使用 ForEach
)。
步骤 2:从 ToList()
方法开始,CLR 将调用 List 类,并将步骤 1 中创建的 DistinctIterator
作为输入传递。List 类在迭代 DistinctIterator
实例时进行迭代。DistinctIterator
中实现的迭代逻辑将创建一个 Set<TSource>
的新实例,并遍历原始列表,将迭代的项添加到其创建的 Set<TSource>
实例中。内部 Set<TSource>
类将使用 Add 和 Find 方法将项目添加到内部槽数组中,前提是数组槽中没有重复项。这将一直持续到 CLR 到达列表末尾,并获得一个包含唯一项的列表。
因此,Distinct 方法在列表上的工作原理如下:
private static IEnumerable<TSource> DistinctIterator<TSource>(
IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
{
Set<TSource> iteratorVariable0 = new Set<TSource>(comparer);
foreach (TSource iteratorVariable1 in source)
{
if (iteratorVariable0.Add(iteratorVariable1))
{
yield return iteratorVariable1;
}
}
}
ElementAt
此扩展方法返回序列中指定索引处的元素。阅读 更多。
此扩展方法的签名如下:
public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index)
public static TSource ElementAtOrDefault<TSource>(this IEnumerable<TSource> source, int index)
以下显示了 IEnumerable<TSource>
接口的 ElementAt
扩展方法的示例。
using System;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> numbers = new List<string>()
{
"One","Two","Three"
};
var elementAt = numbers.ElementAt(1);
Console.WriteLine(elementAt);
}
}
}
此程序创建一个 numbers 列表,该列表存储 One、Two 和 Three。从这个 numbers 列表中,我尝试访问存储在位置(数组位置)1 的元素,并将其存储在 elementAt
变量中以显示在控制台上。此程序将产生以下输出:
Two
要执行 ElementAt
方法,CLR 将执行以下操作:
步骤 1:CLR 将在执行 ElementAt<TSource>(this IEnumerable<TSource> source, int index)
扩展方法时,从 System.Collections.Generic.List`1<T>
类调用 get_Item
方法。使用 ILDasm.exe 程序从 mscorlib.dll 程序集的 System.Linq.Enumerable
类的 ElementAt
方法中提取了以下代码:
.method public hidebysig static !!TSource ElementAt<TSource>(
class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> source,
int32 index) cil managed
{
IL_0018: ldloc.0
IL_0019: ldarg.1
IL_001a: callvirt instance !0 class
[mscorlib]System.Collections.Generic.IList`1<!!TSource>::get_Item(int32)
IL_001f: ret
} // end of method Enumerable::ElementAt
从上面的代码可以看出,CLR 将使用从 ElementAt
方法的调用者提供的索引作为输入参数,在标签 IL_001a 处调用 System.Collections.Generic.List`1<T>
类的 get_Item(int32)
方法。
步骤 2:System.Collections.Generic.List`1<T>
类的 get_Item(int32)
方法将加载此类的 _items
数组(在以下 IL 代码的标签 IL_000f 中),然后它将加载参数(在以下 IL 代码的标签 IL_0014 中)以获取索引,该索引稍后将用于根据索引从 _items
数组访问项。如果我们使用 ILDasm 查看 mscorlib.dll 中 System.Collections.Generic.List`1<T>
类的 get_Item(int32)
方法的 IL 代码:
.method public hidebysig newslot specialname virtual final
instance !T get_Item(int32 index) cil managed
{
IL_000e: ldarg.0
IL_000f: ldfld !0[] class System.Collections.Generic.List`1<!T>::_items
IL_0014: ldarg.1
IL_0015: ldelem !T
IL_001a: ret
} // end of method List`1::get_Item
从上面的代码可以看出,在 IL_000f 标签中使用的 ldfld
IL 指令将加载 List<T> 类的 _items 字段,而在 IL_0014 标签处,它将加载参数 1,即索引,它将使用 IL 指令 ldelem
(在 IL_0015 中使用)来访问 _items 数组中的项。
Empty
Empty 扩展方法返回一个具有指定类型参数的空 IEnumerable<T>
。阅读 更多。
此扩展方法的签名如下:
public static IEnumerable<TResult> Empty<TResult>()
以下示例显示了 Empty
方法的用法:
using System;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
var emptyList = Enumerable.Empty<int>();
Console.WriteLine(emptyList.Count());
}
}
}
在上面的代码中,我使用 Empty<int>()
方法创建了一个 int
类型的空列表。此程序将产生以下输出:
0
当 CLR 执行 Empty 扩展方法时,它将执行以下操作来执行操作。
步骤 1:CLR 将在执行 Empty<TResult>()
方法时,从 System.Core.dll 程序集 System.Linq
命名空间下的 EmptyEnumerable`1<!!TResult>
内部类调用 get_Instance()
方法。此类包含一个给定类型(!!TResult
)的数组字段和一个 Instance
属性,该属性返回数组字段。以下 Empty
方法的 IL 代码是使用 ILDasm.exe 程序从 System.Core.dll 程序集中反编译的。
.method public hidebysig static class
[mscorlib]System.Collections.Generic.IEnumerable`1<!!TResult>
Empty<TResult>() cil managed
{
IL_0000: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!0>
class System.Linq.EmptyEnumerable`1<!!TResult>::get_Instance()
IL_0005: ret
} // end of method Enumerable::Empty
步骤 2:System.Linq.EmptyEnumerable`1<!!TResult>
的 Instance 属性将调用 get_Instance()
方法。get_Instance()
方法将创建一个包含 0 个项的数组。CLR 将首先使用 ldc.i4.0 IL 指令将 0 推送到堆栈上(在以下代码的标签 IL_0007 中)。使用 newarr
IL 指令,CLR 将创建一个包含 0 个项的新数组并将其推送到堆栈上。在标签 IL_000d 处,CLR 将使用 stsfld
来替换字段值,即实例字段的值,使用堆栈中的值。
.method public hidebysig specialname static
class [mscorlib]System.Collections.Generic.IEnumerable`1<!TElement>
get_Instance() cil managed
{
// Code size 24 (0x18)
.maxstack 8
IL_0000: ldsfld !0[] class System.Linq.EmptyEnumerable`1<!TElement>::'instance'
IL_0005: brtrue.s IL_0012
IL_0007: ldc.i4.0
IL_0008: newarr !TElement
IL_000d: stsfld !0[] class System.Linq.EmptyEnumerable`1<!TElement>::'instance'
IL_0012: ldsfld !0[] class System.Linq.EmptyEnumerable`1<!TElement>::'instance'
IL_0017: ret
} // end of method EmptyEnumerable`1::get_Instance
Except
Except
方法可用于从一个列表中移除另一个列表的项。它产生两个序列的集合差。阅读 更多
此扩展方法的签名如下:
public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first,
IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
上述两个扩展方法将执行以下操作:
- 第一种版本通过使用默认相等比较器比较值来产生两个序列的集合差。
- 第二种版本通过使用指定的
IEqualityComparer<T>
比较值来产生两个序列的集合差。
Except
方法可用于从一个列表中移除另一个列表的项。例如,如果我们有一个列表 A,其中包含 {1,2,3,4,5,6,7},而列表 B 包含 {1,2,3},那么 A 减去 B 将产生 {4,5,6,7}。以下程序使用 Except
来展示其用法:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> firstNumbers = new List<int>()
{
1,2,3,4,5,6,7
};
IList<int> secondNumbers = new List<int>()
{
1,2,3
};
var result = firstNumbers.Except(secondNumbers).ToList();
result.ForEach(x => Console.WriteLine(x));
}
}
}
此程序将产生以下输出:
4
5
6
7
当上述程序运行时,它将产生 {4,5,6,7}。下图显示了 Except
扩展方法的工作原理:
CLR 将按以下方式执行 Except 方法:
步骤 1:CLR 将原始列表复制到 Except 方法中作为输入,该方法将内部调用 Except<TSource>(this IEnumerable<TSource> first, this IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
方法,它将实例化 ExceptIterator<TSource>( this IEnumerable<TSource> first, this IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
迭代器类。由于延迟执行,此迭代器在调用列表上的 ToList()
方法或使用 ForEach
语句之前不会执行。
步骤 2:当 CLR 执行 ToList()
方法时,它将调用 List 类,并将步骤 1 中创建的 ExceptIterator
实例作为输入传递。List 类在迭代列表时调用 ExceptIterator
方法。ExceptIterator
方法创建一个 Set<TSource>
类型的新实例,并遍历第二个列表,将迭代的项目添加到其创建的 Set 实例中。内部方法将使用 Add 和 Find 方法将项目添加到内部槽数组中,前提是槽中没有重复项。这将一直持续到编译器到达第二个列表的末尾。在第二个循环中,CLR 将遍历第一个列表,并尝试将迭代的项目添加到此步骤中创建的 Set 对象中。如果第二个列表中的项目不存在于 Set 对象中,它将返回该项目,并继续遍历第一个列表,直到完成。
ExceptIterator
的近似代码如下:
private static IEnumerable<TSource> ExceptIterator<TSource>(
IEnumerable<TSource> first,
IEnumerable<TSource> second,
IEqualityComparer<TSource> comparer)
{
Set<TSource> iteratorVariable0 = new Set<TSource>(comparer);
foreach (TSource local in second)
{
iteratorVariable0.Add(local);
}
foreach (TSource iteratorVariable1 in first)
{
if (!iteratorVariable0.Add(iteratorVariable1))
{
continue;
}
yield return iteratorVariable1;
}
}
因此,Distinct
方法在列表上的工作原理如下:
First
它返回序列的第一个元素。阅读 更多。
签名如下:
public static TSource First<TSource>(this IEnumerable<TSource> source)
public static TSource First<TSource>(this IEnumerable<TSource> source,Func<TSource, bool> predicate)
上述两个 First 扩展方法将执行以下操作:
- 此扩展方法的第一种版本将查找项序列中的第一个项。
- 此扩展方法的第二种版本将查找列表中满足谓词条件的第一个项。
我编写了一个小程序来解释 Enumerable
类的这两种版本的 First
扩展方法的工作步骤。
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int>()
{
1,2,3,4,5,6,7
};
var firstItem = numbers.First();
var firstItemBasedOnConditions = numbers.First(item => item > 3);
Console.WriteLine("{0}\n{1}",
firstItem,
firstItemBasedOnConditions
);
}
}
}
此程序将产生以下输出:
1
4
当 CLR 执行 First 扩展方法的第一种版本时,它将执行以下步骤来执行操作:
- CLR 将原始列表作为输入参数发送到
First <TSource>(this IEnumerable<TSource> source)
方法。 - 此方法将返回原始列表的第一个项,或者遍历原始列表并将迭代的第一个项作为结果返回。
这的近似代码如下:
public static TSource First<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
if (list.Count > 0)
{
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
return enumerator.Current;
}
}
}
}
对于 First 扩展方法的第二种版本,CLR 将执行以下步骤:
- 编译器将使用编译时匿名方法
(item => item > 3)
构造一个方法<Main>b_1
。CLR 将此<Main>b_1
方法传递给MulticastDelegate
类以构造它的实例。 - First 方法将遍历列表并根据谓词与序列中的每个元素进行匹配。它在第一次匹配时返回,否则继续进行,直到找到匹配项或未找到。
的近似代码,
public static TSource First<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
foreach (TSource local in source)
{
if (predicate(local))
{
return local;
}
}
}
FirstOrDefault
它返回序列的第一个元素,如果未找到元素,则返回默认值。阅读 更多。
FirstOrDefault
扩展的方法签名如下:
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
上述两个扩展方法将执行以下操作:
- 它返回序列的第一个元素,如果序列不包含任何元素,则返回默认值。
- 它返回序列中满足条件的第一个元素,如果未找到这样的元素,则返回默认值。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> firstNumbers = new List<int>();
IList<int> secondNumbers = new List<int>()
{
1,2,3,4,5,6,7
};
var firstItemOfFirstList = firstNumbers.FirstOrDefault();
var firstItemIfFirstListBasedOnConditions =
firstNumbers.FirstOrDefault(item => item > 3);
var firstItemOfSecondList = secondNumbers.FirstOrDefault();
var firstItemOfSecondListBasedOnConditions =
secondNumbers.FirstOrDefault(item => item > 3);
Console.WriteLine("{0}\n{1}\n{2}\n{3}",
firstItemOfFirstList,
firstItemIfFirstListBasedOnConditions,
firstItemOfSecondList,
firstItemOfSecondListBasedOnConditions
);
}
}
}
此程序将产生以下输出:
0
0
1
4
FirstOrDefault
扩展方法第一种版本的近似代码:
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
if (list.Count > 0)
{
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
return enumerator.Current;
}
}
}
return default(TSource);
}
FirstOrDefault
扩展方法第二种版本的近似代码:
public static TSource FirstOrDefault<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
foreach (TSource local in source)
{
if (predicate(local))
{
return local;
}
}
return default(TSource);
}
Union
Union
方法将集合的并集(记为 ?)定义为集合中所有唯一元素的集合。阅读 更多。例如,如果我们有两个集合,A={1,2,3,4,5,6,7} 和 B={5,6,7,8,9},那么这些集合的并集将是 A u B ={1,2,3,4,5,6,7,8,9}。
在 .NET 中,Union
方法将执行与上述图完全相同的工作,它将连接两个列表并创建一个新列表。
public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first,
IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
以下程序展示了 Union
操作的用法:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> firstList = new List<int>()
{
1,2,3,4
};
IList<int> secondList = new List<int>()
{
7,9,3,4,5,6,7
};
var result = firstList.Union(secondList);
result.ToList().ForEach(x => Console.WriteLine(x));
}
}
}
此程序将产生以下输出:
1
2
3
4
7
9
5
6
当 CLR 执行 Union 方法时,如下所示:
步骤 1:Union 方法将实例化一个 UnionIterator<TSource>
,该迭代器将包含 firstList
和 secondList
,以及 IEqualityComparer
的 null 值,因为上述程序未提供任何内容。
步骤 2:由于延迟执行,此 UnionIterator<TSource>
将在 CLR 开始执行 ToList()
方法时执行。在 UnionIterator<TSource>
内部,将实例化一个 Set<TSource>
类的新实例,该实例将用于查找两个列表中唯一的项。
UnitonIterator
的近似代码如下:
private static IEnumerable<TSource> UnionIterator<TSource>(
IEnumerable<TSource> first,
IEnumerable<TSource> second,
IEqualityComparer<TSource> comparer)
{
Set<TSource> iteratorVariable0 = new Set<TSource>(comparer);
foreach (TSource iteratorVariable1 in first)
{
if (iteratorVariable0.Add(iteratorVariable1))
{
yield return iteratorVariable1;
}
}
foreach (TSource iteratorVariable2 in second)
{
if (!iteratorVariable0.Add(iteratorVariable2))
{
continue;
}
yield return iteratorVariable2;
}
}
Intersect
它生成两个序列的集合交集。阅读 更多。此扩展方法的签名如下:
public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first,
Enumerable<TSource> second,IEqualityComparer<TSource> comparer)
上述扩展方法将执行以下操作:
- 它通过使用默认相等比较器比较值来生成两个序列的集合交集。
- 它通过使用指定的
IEqualityComparer<T>
比较值来生成两个序列的集合交集。
它生成两个序列的集合交集。交集运算将产生两个列表中共有的元素。例如,如果我们有一个列表 A,其中包含 {1, 2, 3, 4, 5},而列表 B 包含 {4, 5},那么这两个列表 A n B 的交集将产生 {4, 5}
让我们看下面的程序,该程序创建了两个列表,分别名为 listA
(值为 1, 2, 3, 4, 5)和 listB
(值为 4, 5)。我正在使用 Enumerable
扩展方法 Intersect
在这两个列表之间进行交集操作。
using System;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> listA = new List<int>() { 1, 2, 3, 4, 5 };
IList<int> listB = new List<int>() { 4, 5 };
var intersectResult = listA.Intersect(listB);
intersectResult.ToList().ForEach(x => Console.Write("{0}\t",x));
Console.WriteLine();
}
}
}
此程序将产生以下输出:
4 5
要执行 Intersect 扩展方法,CLR 将执行以下操作:
步骤 1:CLR 将实例化 IntersectIterator<TSource>
的实例并返回给调用者。由于延迟执行,此迭代器在调用 ToList()
方法之前不会执行。
步骤 2:从 IntersectIterator<TSource>
开始,CLR 将创建 Set<TSource>
对象的实例,该对象用于存储第二个列表中的所有项。然后,CLR 将遍历第一个列表,并尝试从 Set<TSource>
对象中删除第一个列表中的每个项。如果它可以删除,它将返回 firstlist
的项,否则它将继续遍历第一个列表,直到它可以从 Set 中删除。以下代码显示了 IntersectIterator
方法的近似代码:
private static IEnumerable<TSource> IntersectIterator<TSource>(
IEnumerable<TSource> first,
IEnumerable<TSource> second,
IEqualityComparer<TSource> comparer)
{
Set<TSource> iteratorVariable0 = new Set<TSource>(comparer);
foreach (TSource local in second)
{
iteratorVariable0.Add(local);
}
foreach (TSource iteratorVariable1 in first)
{
if (!iteratorVariable0.Remove(iteratorVariable1))
{
continue;
}
yield return iteratorVariable1;
}
}
Last
它返回序列的最后一个元素。阅读 更多
此扩展方法的签名如下:
public static TSource Last<TSource>(this IEnumerable<TSource> source)
public static TSource Last<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
上述两个 First 扩展方法将执行以下操作
- 此扩展方法的第一种版本将查找项序列中的第一个项。
- 此扩展方法的第二种版本将查找列表中满足谓词条件的第一个项。
我编写了一个小程序来解释 Enumerable
类的这两种版本的 Last
扩展方法的工作步骤。
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int>()
{
1,2,3,4,5,6,7
};
var lastItem = numbers.Last();
var lastItemBasedOnConditions = numbers.Last(item => item > 3);
}
}
}
当 CLR 执行 Last 扩展方法的第一种版本时,如下所示:
- 原始列表将被作为输入参数传递给
Last <TSource>(this IEnumerable<TSource> source)
方法。 - 此方法将通过列表返回的
Enumerator
对象遍历列表,并检查调用其MoveNext()
方法时枚举器是否返回 true 值,否则返回 false,即序列中没有元素。
这的近似代码如下:
public static TSource Last<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
int count = list.Count;
if (count > 0)
{
return list[count - 1];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
TSource current;
do
{
current = enumerator.Current;
}
while (enumerator.MoveNext());
return current;
}
}
}
throw Error.NoElements();
}
对于 Any
扩展方法的第二种版本,CLR 将执行以下步骤:
- 编译器将在编译时使用匿名方法(item => item > 3)构造一个方法
<Main>b_1
。CLR 将此<Main>b_1
方法传递给MulticastDelegate
类以构造它的实例,并将此<Main>b_1
传递给 Last 扩展方法。 - CLR 将遍历列表并根据谓词中提供的条件与序列中的每个元素进行匹配。它在第一次匹配时返回,否则继续进行,直到找到匹配项或未找到。
的近似代码,
public static TSource Last<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
TSource local = default(TSource);
bool flag = false;
foreach (TSource local2 in source)
{
if (predicate(local2))
{
local = local2;
flag = true;
}
}
return local;
}
LastOrDefault
它返回序列的最后一个元素,如果未找到元素,则返回默认值。阅读 更多
此扩展方法的签名如下:
public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source)
public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
上述扩展方法将执行以下操作:
- 它返回序列的最后一个元素,如果序列不包含任何元素,则返回默认值。
- 它返回序列中满足条件的最后一个元素,如果未找到这样的元素,则返回默认值。
我编写了一个示例来讨论 LastOrDefault
扩展方法:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> firstNumbers = new List<int>();
IList<int> secondNumbers = new List<int>()
{
1,2,3,4,5,6,7
};
var lastItemOfFirstList = firstNumbers.LastOrDefault();
var lastItemIfFirstListBasedOnConditions =
firstNumbers.LastOrDefault(item => item > 3);
var lastItemOfSecondList = secondNumbers.LastOrDefault();
var lastItemOfSecondListBasedOnConditions =
secondNumbers.LastOrDefault(item => item > 3);
Console.WriteLine("{0}\n{1}\n{2}\n{3}",
lastItemOfFirstList,
lastItemIfFirstListBasedOnConditions,
lastItemOfSecondList,
lastItemOfSecondListBasedOnConditions
);
}
}
}
此程序将产生以下输出:
0
0
7
7
LastOfDefault
扩展方法第一种版本的近似代码如下:
public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
int count = list.Count;
if (count > 0)
{
return list[count - 1];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
TSource current;
do
{
current = enumerator.Current;
}
while (enumerator.MoveNext());
return current;
}
}
}
return default(TSource);
}
LastOfDefault
扩展方法第二种版本的近似代码如下:
public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
TSource local = default(TSource);
foreach (TSource local2 in source)
{
if (predicate(local2))
{
local = local2;
}
}
return local;
}
LongCount
它返回一个 Int64,表示序列中的元素数。阅读 更多
此扩展方法的签名如下:
public static long LongCount<TSource>(this IEnumerable<TSource> source)
public static long LongCount<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
上述扩展方法将按以下方式执行:
- 返回一个
Int64
,表示序列中的元素总数。 - 返回一个
Int64
,表示序列中有多少个元素满足某个条件。
让我们看一个例子:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> firstList = new List<int>()
{
1,2,3,4
};
Console.WriteLine(firstList.LongCount());
}
}
}
程序将产生以下输出:
4
LongCount
扩展方法第一种版本的近似代码如下:
public static long LongCount<TSource>(this IEnumerable<TSource> source)
{
long num = 0L;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
num += 1L;
}
}
return num;
}
LongCount
扩展方法第二种版本的近似代码如下:
public static long LongCount<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
long num = 0L;
foreach (TSource local in source)
{
if (predicate(local))
{
num += 1L;
}
}
return num;
}
Max
在 .NET 中,Enumerable
类定义了五个重载的 Max
扩展方法。阅读 更多。
public static int Max(this IEnumerable<int> source)
public static decimal Max<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector)
Max 扩展方法的示例如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int>()
{
1,2,3,4,5,6,7,8,9,10
};
Console.WriteLine("Max of the numbers :{0}", numbers.Max());
Console.WriteLine("Max of the original numbers x2 :{0}", numbers.Max(x => x * 2));
}
}
}
上述程序将产生以下输出:
Max of the numbers :10
Max of the original numbers x2 :20
因此,当 CLR 在上述程序中找到 Max 方法的第一种版本时,它将执行以下步骤来执行操作:
步骤 1:CLR 将原始列表作为输入传递给 Max 方法。
步骤 2:Max
方法将遍历列表并执行 Max 操作。
Max 扩展方法的第二种版本将执行以下步骤:
步骤 1:编译器将使用匿名方法(x => x * 2)构造一个方法 <Main>b_1
。CLR 将此 <Main>b_1
方法传递给 MulticastDelegate
类以构造它的实例,并调用列表的 Select 方法,该方法将接受原始列表和 MulticastDelegate
类的实例作为输入。然后,它将返回相关的迭代器实例,例如,对于上面的示例,它将是列表的 WhereSelectListIterator<tsource,tresult>
作为输出。
步骤 2:然后,它将调用 Max
方法,该方法仅接受迭代器。此迭代器将包含原始列表和 MulticastDelegate
实例。在 Max
方法中,ForEach
方法将遍历列表并执行最大值计算。
下图显示:
Min
此扩展方法将找出列表的最小值。阅读 更多。Min 扩展方法的签名如下:
public static int Min(this IEnumerable<int> source)
public static int Min<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
Min 扩展方法的示例如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int>()
{
1,2,3,4,5,6,7,8,9,10
};
Console.WriteLine("Min of the numbers :{0}", numbers.Min());
Console.WriteLine("Min of the original numbers x2 :{0}", numbers.Min(x => x * 2));
}
}
}
程序将产生以下输出:
Min of the numbers :1
Min of the original numbers x2 :2
因此,当 CLR 在上述程序中找到 Min 扩展方法的第一种版本时,它将执行以下步骤来执行操作:
- CLR 将原始列表作为输入传递给 Min 扩展方法。
- Min 方法将遍历列表并执行最小化计算操作。
Min 扩展方法的第二种版本将执行以下步骤:
步骤 1:CLR 将调用列表的 Select 方法,该方法将接受原始列表和使用 <Main>b_1
方法(该方法将在编译时基于匿名方法 (x=>x*2) 创建)实例化的 MulticastDelegate
实例。它将返回适当的迭代器,即 WhereSelectListIterator<tsource,tresult>
,并将其作为输入传递给内部 Min 方法。
步骤 2:在 Min 方法内部,CLR 将执行最小值计算操作并返回结果。
下图显示:
OfType
此扩展方法使用延迟执行,根据指定的类型过滤 IEnumerable
的元素。直接返回值是迭代器类的对象,该对象存储了执行操作所需的所有信息。阅读 更多。
此扩展方法的签名如下:
public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source)
OfType<TResult>
的示例
using System;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<object> numbers = new List<object>()
{
"One",
"Two",
1,
2,
"Three",
new Person
{
Name="A Person"
}
};
var filteredNumbers = numbers.OfType<string>();
filteredNumbers.ToList().ForEach(x => Console.Write("{0}\t", x));
Console.WriteLine();
}
}
public class Person
{
public string Name { get; set; }
}
}
上述程序将从 numbers 列表中过滤字符串值,因为我在 OfType
方法中设置了 string。因此,程序将产生以下输出:
One Two Three
步骤 1:CLR 将将序列(例如,上面示例中的 numbers)作为输入传递给 OfType 方法。在 OfType
方法内部,CLR 将实例化 OfTypeIterator
,该迭代器将包含原始序列。OfType 方法的近似代码如下:
public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source)
{
return OfTypeIterator<TResult>(source);
}
步骤 2:CLR 将 OfTypeIterator<TResult>
类的实例传递给 ToList()
方法,该方法将此迭代器传递给 List 类,并根据 OfTypeIterator
中实现的迭代逻辑处理操作,并生成范围序列作为输出。
RangeIteraor 的近似代码如下:
private static IEnumerable<TResult> OfTypeIterator<TResult>(IEnumerable source)
{
IEnumerator enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
object current = enumerator.Current;
if (current is TResult)
{
yield return (TResult) current;
}
}
}
Range
它生成指定范围内的整数序列,通过使用延迟执行来实现。直接返回值是相关迭代器实例的一个实例,该实例存储了执行操作所需的所有信息。此扩展方法的签名如下:
public static IEnumerable<int> Range(int start, int count)
阅读 更多。
此方法将根据起始数字创建 int
项列表,直到计数定义的次数。
using System;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
Enumerable.Range(1, 10).ToList().ForEach(x => Console.Write("{0}\t", x));
}
}
}
程序将产生以下输出:
1 2 3 4 5 6 7 8 9 10
CLR 将执行以下操作:
步骤 1:CLR 将起始元素和次数或生成序列的长度作为输入传递给 Range 方法。在 Range 方法内部,CLR 将返回 RangeIterator<int>
,该迭代器将包含所有相关信息,如起始元素和序列长度。Range 方法的近似代码如下:
public static IEnumerable<int> Range(int start, int count)
{
long num = (start + count) - 1L;
if ((count < 0) || (num > 0x7fffffffL))
{
throw Error.ArgumentOutOfRange("count");
}
return RangeIterator(start, count);
}
RangeIterator<int>
在 CLR 调用 ToList()
方法之前不会被执行(由于延迟执行)。
步骤 2:CLR 将此 RangeIterator<int>
传递给 ToList()
方法,该方法将此迭代器实例传递给 List
类,并根据 RangeIterator<int>
类中实现的迭代逻辑处理操作,并生成范围序列作为输出。RangeIteraor<int>
的近似代码如下:
private static IEnumerable<int> RangeIterator(int start, int count)
{
int iteratorVariable0 = 0;
while (true)
{
if (iteratorVariable0 >= count)
{
yield break;
}
yield return (start + iteratorVariable0);
iteratorVariable0++;
}
}
Repeat
它生成一个包含一个重复值的序列,通过使用延迟执行来实现。直接返回值是相关迭代器类型的一个对象,该对象存储了执行操作所需的所有信息。阅读 更多
此扩展方法的签名如下:
public static IEnumerable<TResult> Repeat<TResult>(TResult element, int count)
它将生成由 TResult
类型定义的数字序列,重复次数由 count 测量。
using System;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
Enumerable.Repeat(1, 5).ToList().ForEach(x=>Console.Write("{0}\t",x));
}
}
}
Enumerable
类的 Repeat 方法将生成 1 的序列,重复 5 次。它将产生以下输出:
1 1 1 1 1
步骤 1:CLR 将要重复的元素和重复次数作为输入传递给 Repeat 方法。在 Repeat 方法内部,它将构造 RepeatIterator<TResult>
迭代器,该迭代器将包含生成序列的所有相关信息。
步骤 2:CLR 将此 RepeatIterator<TResult>
实例传递给 ToList()
方法,该方法将此迭代器传递给 List 类,并根据 RepeatIterator<TResult>
中实现的迭代逻辑处理操作,并生成重复序列作为输出。
Repeat 方法的近似代码如下:
private static IEnumerable<TResult> RepeatIterator<TResult>(TResult element, int count)
{
int iteratorVariable0 = 0;
while (true)
{
if (iteratorVariable0 >= count)
{
yield break;
}
yield return element;
iteratorVariable0++;
}
}
Reverse
它反转序列中元素的顺序,通过使用延迟执行来实现。直接返回值是迭代器类型的一个对象,该对象存储了执行操作所需的所有信息。阅读 更多
与 OrderBy
不同,此排序方法不考虑实际值本身来确定顺序。相反,它只是按照底层源生成它们的相反顺序返回元素。
public static IEnumerable<TSource> Reverse<TSource>(this IEnumerable<TSource> source)
它将反转原始列表,以下示例显示了 Reverse
扩展方法的用法:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int>() { 1, 2, 3, 4, 5 };
var reverseNumbers = numbers.Reverse();
var result = reverseNumbers.ToList();
result.ForEach(x => Console.Write("{0}\t", x));
Console.WriteLine();
}
}
}
此程序将产生以下输出:
5 4 3 2 1
当 CLR 执行上述程序时,为了处理 Reverse 方法,它将执行以下步骤:
步骤 1:CLR 将原始序列(在本例中为 numbers 对象)作为输入传递给 Reverse
方法。在 Reverse 方法内部,它将构造 ReverseIterator<TSource>
迭代器,该迭代器将包含与原始序列相关的所有信息。
步骤 2:CLR 将 ReverseIterator<TSource>
实例传递给 ToList()
方法,该方法将此迭代器传递给 List
类,并根据 ReverseIterator<TSource>
中实现的迭代逻辑处理操作,并生成反向序列作为输出。
ReverseIterator<TSource>
的近似代码:
private static IEnumerable<TSource> ReverseIterator<TSource>(
IEnumerable<TSource> source)
{
Buffer<TSource> iteratorVariable0 = new Buffer<TSource>(source);
int index = iteratorVariable0.count - 1;
while (true)
{
if (index < 0)
{
yield break;
}
yield return iteratorVariable0.items[index];
index--;
}
}
Single
此扩展方法返回序列中一个特定的单个元素。阅读 更多。
此扩展方法的签名如下:
public static TSource Single<TSource>(this IEnumerable<TSource> source)
public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source)
public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source,Func<TSource, bool> predicate)
上述扩展方法将执行以下操作:
- 它返回序列的唯一元素,如果序列中的元素不是正好一个,则会引发异常。
- 它返回序列中满足指定条件的唯一元素,如果存在多个此类元素,则会引发异常。
我编写了一个小程序来测试 Single
扩展方法。在此程序中,我将对 IList<string>
对象 numbers 使用 Single 方法,该对象只包含一个项 One。以下示例将返回 One 作为输出,因为这是列表中恰好一个的项,而 Single 方法仅适用于其中恰好包含 1 个项的 List 对象,
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> numbers = new List<string>
{
"One"
};
var result = numbers.Single();
Console.WriteLine("{0}", result);
}
}
}
上述程序将产生以下输出:
One
如果我修改上述代码并在 numbers 列表中添加一个新项并执行程序,我们将收到以下错误消息:
Unhandled Exception: System.InvalidOperationException: Sequence contains more th
an one element
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at Chapter_5.Program.Main(String[] args) in J:\Book\How Does it Work in C#\Bo
ok-Projects\HDIWIC\Chapter-5\Program.cs:line 16
现在让我们找出它在后台是如何工作的。
第一步:CLR 将创建一个新的 List<string>
对象,该对象复制自原始列表,并检查新列表是否为 null。如果不是 null,它将检查列表中的项目数。如果列表中的项目数为 0,CLR 将抛出异常,否则它将返回列表中的第一个也是唯一一个项目。近似代码如下:
public static TSource Single<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
switch (list.Count)
{
case 0:
throw Error.NoElements();
case 1:
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (!enumerator.MoveNext())
{
throw Error.NoElements();
}
TSource current = enumerator.Current;
if (!enumerator.MoveNext())
{
return current;
}
}
}
throw Error.MoreThanOneElement();
}
让我们尝试使用谓词函数来使用 Single
扩展方法。我写了一个小程序如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> numbers = new List<string>
{
"One","Four"
};
var result = numbers.Single(x => x.Length > 3);
Console.WriteLine("{0}", result);
}
}
}
此程序将产生以下输出:
Four
如果我通过添加一个长度超过三个字符的新项来更改 numbers 列表,程序将通过抛出以下异常而失败:
Unhandled Exception: System.InvalidOperationException: Sequence contains more th
an one matching element
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predic
ate)
at Chapter_5.Program.Main(String[] args) in J:\Book\How Does it Work in C#\Bo
ok-Projects\HDIWIC\Chapter-5\Program.cs:line 16
我写了一个 SingleOrDefault
扩展方法的例子如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> listStringWithoutItem = new List<string>();
IList<string> listStringWithItem = new List<string>() { "One" };
IList<int> listInt = new List<int>();
IList<char> listChar = new List<char>();
IList<long> listLong = new List<long>();
IList<double> listDouble = new List<double>();
var resultStringWithoutItem = listStringWithoutItem.SingleOrDefault();
var resultStringWithItem = listStringWithItem.SingleOrDefault();
var resultInt = listInt.SingleOrDefault();
var resultChar = listChar.SingleOrDefault();
var resultLong = listLong.SingleOrDefault();
var resultDouble = listDouble.SingleOrDefault();
Console.WriteLine("string : {0}", resultStringWithoutItem);
Console.WriteLine("string : {0}", resultStringWithItem);
Console.WriteLine("int : {0}", resultInt);
Console.WriteLine("char : {0}", resultChar);
Console.WriteLine("long : {0}", resultLong);
Console.WriteLine("double : {0}", resultDouble);
}
}
}
以上将产生以下输出:
string :
string : One
int : 0
char :
long : 0
double : 0
CLR 将按照以下步骤执行 SingleOrDefault
扩展:
第一步:CLR 将检查列表是否为 null。如果不是,则将原始列表复制到临时列表中。它将检查列表中的项目数,如果项目数为 0,则 CLR 将返回提供类型的默认值,例如,对于 listStringWithoutItem.SingleOrDefault<string>()
,默认值为 string,或者从列表中推断出的类型,例如,如果 listStringWithoutItem
是 IList<string>
类型,则推断的类型将是 string。完整的列表默认值表 在此。
SingleOrDefault
扩展方法的近似代码如下:
public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
switch (list.Count)
{
case 0:
return default(TSource);
case 1:
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (!enumerator.MoveNext())
{
return default(TSource);
}
TSource current = enumerator.Current;
if (!enumerator.MoveNext())
{
return current;
}
}
}
throw Error.MoreThanOneElement();
}
Skip
它会跳过序列中指定数量的元素,然后返回剩余的元素,这是通过使用延迟执行来实现的。立即返回的值是相关类型的一个对象,它存储了执行操作所需的所有信息。阅读 更多。
Skip 方法将遍历列表并从列表的开头跳过指定数量的项目。指定数量将作为方法的参数接受。
public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count)
以下程序将创建一个字符串类型的列表,该列表将包含 One、Two、Three、Four 和 Five 作为其项目。在该列表上,我通过提供 2 作为要跳过的项目数量来应用 skip 操作。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> numbers = new List<string>()
{
"One","Two","Three", "Four","Five"
};
var result = numbers.Skip(2);
result.ToList().ForEach(number => Console.WriteLine(number));
}
}
}
程序将产生以下输出:
Three
Four
Five
因此,CLR 将找到上述代码,特别是 numbers.Skip(1),它将:
第一步:CLR 将从 System.Linq
命名空间进入 Enumerable
类的 Skip 方法。此方法将返回 SkipIterator<TSource>
的实例,该实例将保存原始列表和用于定义要跳过的项目数的计数。
第二步:由于延迟执行模式,此 SkipIterator<TSource>
将在通过 ToList()
方法进行迭代时执行。因此,在 SkipIterator
Enumerator
对象的当前位置。当项目数变为 0 时,它将再次遍历列表以返回列表中的剩余项目。
SkipIterator
的近似代码如下:
private static IEnumerable<TSource> SkipIterator<TSource>(
IEnumerable<TSource> source, int count)
{
using (IEnumerator<TSource> iteratorVariable0 = source.GetEnumerator())
{
while ((count > 0) && iteratorVariable0.MoveNext())
{
count--;
}
if (count <= 0)
{
while (iteratorVariable0.MoveNext())
{
yield return iteratorVariable0.Current;
}
}
}
}
SkipWhile
此扩展方法将跳过序列中条件为 true 的元素,然后返回剩余的元素。阅读 更多。
此扩展方法的签名如下:
public static IEnumerable<TSource> SkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
public static IEnumerable<TSource> SkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
让我们看一个 SKipWhile
扩展方法的例子,这将有助于理解这个扩展方法:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<string> numbers = new List<string>()
{
"One","Two","Three", "Four","Five"
};
var result = numbers.SkipWhile(number => number.Length == 3);
result.ToList().ForEach(number => Console.WriteLine(number));
}
}
}
上述程序将产生以下输出:
Three
Four
Five
输出显示结果列表不包含长度等于 3 的项目。因此,当 CLR 找到 SkipWhile
方法时,它将执行以下操作:
第一步:编译器将使用匿名方法 (number => number.Length == 3) 在编译时构造一个方法 <Main>b_1
。CLR 将此 <Main>b_1
方法传递给 MulticastDelegate
类以实例化一个实例。因此,CLR 将原始列表和谓词作为输入传递给 SkipWhile
方法,它将返回 SkipWhileIterator
,该迭代器将保存原始列表和 <Main>b_1
作为谓词。
第二步:在 SkipWhileIterator
内部,CLR 将逐个循环遍历原始列表,并对项目执行谓词。如果谓词返回 false,则它将该项目作为 SkipWhile
方法的结果项返回;否则,如果它返回 true,则它将继续遍历列表直到完成。
SkipWhileIterator
的近似代码如下:
private static IEnumerable<TSource> SkipWhileIterator<TSource>(
IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
bool iteratorVariable0 = false;
foreach (TSource iteratorVariable1 in source)
{
if (!iteratorVariable0 && !predicate(iteratorVariable1))
{
iteratorVariable0 = true;
}
if (iteratorVariable0)
{
yield return iteratorVariable1;
}
}
}
Sum
要对列表中的所有项目求和,我们可以使用此 Sum
扩展方法。阅读 更多。这些方法的签名如下:
public static int Sum(this IEnumerable<int> source)
public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
Min 扩展方法的示例如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int>()
{
1,2,3,4,5,6,7,8,9,10
};
Console.WriteLine("Sum of the numbers :{0}", numbers.Sum());
Console.WriteLine("Sum of the original numbers x2 :{0}",
numbers.Sum(x => x * 2));
}
}
}
程序将产生以下输出:
Sum of the numbers :55
Sum of the original numbers x2 :110
要执行上述程序中使用的 Sum 扩展方法的第一个版本,CLR 将执行以下步骤来执行操作:
第一步:CLR 将原始列表作为输入传递给 Sum 扩展方法。
第二步:在 Sum
方法中,它将遍历列表并对每个项目执行求和,然后生成结果并作为 Sum 的输出返回。
Sum
方法的近似代码如下:
public static int Sum(this IEnumerable<int> source)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
int num = 0;
foreach (int num2 in source)
{
num += num2;
}
return num;
}
要执行 Sum 扩展方法的第二个版本,CLR 将执行以下操作:
第一步:编译器将使用匿名方法 (x => x * 2)
代码创建一个方法 <Main>b_1
。
注意:当我使用 ILDasm.exe 反编译生成的已执行程序时,会有一个方法
.method private hidebysig static int32 <Main>b__1(int32 x) cil managed
{
.maxstack 2
.locals init (
[0] int32 CS$1$0000)
L_0000: ldarg.0
L_0001: ldc.i4.2
L_0002: mul
L_0003: stloc.0
L_0004: br.s L_0006
L_0006: ldloc.0
L_0007: ret
}
CLR 将使用 <Main>b_1
方法创建一个 MulticastDelegate
实例。
第二步:CLR 将在 第一步 中创建的原始列表和 MulticastDelegate
实例作为输入传递给 Sum 方法,该方法将调用 Select 方法,并将原始列表和 MulticastDelegate
对象作为输入。CLR 将实例化相关的迭代器并返回到 Sum 方法。然后 CLR 将调用重载的 Sum()
方法。
第三步:CLR 将根据迭代器遍历原始列表中的项目,并执行给定的选择器(即在 第一步 中创建的委托实例)并对所有修改后的项目求和以完成求和操作。
ThenBy
ThenBy
扩展方法以升序对序列中的元素进行后续排序。此扩展方法通过使用延迟执行来实现。立即返回的值是相关类型的一个对象,它存储了执行操作所需的所有信息。阅读 更多。
ThenBy
扩展方法的签名如下:
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>(this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector)
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>(
this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
public static IOrderedEnumerable<TSource> ThenByDescending<TSource, TKey>(
this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector)
public static IOrderedEnumerable<TSource> ThenByDescending<TSource, TKey>(
this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
我写了这个小程序来解释 ThenyBy
扩展方法:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<Person> persons = new List<Person>()
{
new Person(){ Name="Person F", Address= "Address of F", Id= 111116},
new Person(){ Name="Person G", Address= "Address of G", Id= 111117},
new Person(){ Name="Person C", Address= "Address of C", Id= 111113},
new Person(){ Name="Person B", Address= "Address of B", Id= 111112},
new Person(){ Name="Person D", Address= "Address of D", Id= 111114},
new Person(){ Name="Person A", Address= "Address of A", Id= 111111},
new Person(){ Name="Person E", Address= "Address of E", Id= 111115}
};
var result = persons.OrderBy(person => person.Id).ThenBy(person => person);
foreach (Person person in result)
{
Console.WriteLine("{0,-15} {1,-20}{2,-20}",
person.Name,
person.Address,
person.Id);
}
}
}
public class Person
{
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
public double Id
{
get;
set;
}
}
}
上述程序将产生以下输出:
Person A Address of A 111111
Person B Address of B 111112
Person C Address of C 111113
Person D Address of D 111114
Person E Address of E 111115
Person F Address of F 111116
Person G Address of G 111117
它的工作原理如下:
第一步:代码:ThenBy
方法的近似代码。
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>(
this IOrderedEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
return source.CreateOrderedEnumerable<TKey>(keySelector, null, false);
}
第二步:
IOrderedEnumerable<TElement> IOrderedEnumerable<TElement>.CreateOrderedEnumerable<TKey>(
Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending)
{
return new OrderedEnumerable<TElement, TKey>(this.source, keySelector,
comparer, descending) { parent = (OrderedEnumerable<TElement>) this };
}
第二步:Order 的实现
internal class OrderedEnumerable<TElement, TKey> : OrderedEnumerable<TElement>
{
internal IComparer<TKey> comparer;
internal bool descending;
internal Func<TElement, TKey> keySelector;
internal OrderedEnumerable<TElement> parent;
internal OrderedEnumerable(IEnumerable<TElement> source,
Func<TElement, TKey> keySelector,
IComparer<TKey> comparer,
bool descending)
{
base.source = source;
this.parent = null;
this.keySelector = keySelector;
this.comparer = (comparer != null) ? comparer : ((IComparer<TKey>) Comparer<TKey>.Default);
this.descending = descending;
}
internal override EnumerableSorter<TElement> GetEnumerableSorter(
EnumerableSorter<TElement> next)
{
EnumerableSorter<TElement> enumerableSorter = new
EnumerableSorter<TElement, TKey>(
this.keySelector,
this.comparer,
this.descending, next);
if (this.parent != null)
{
enumerableSorter = this.parent.GetEnumerableSorter(enumerableSorter);
}
return enumerableSorter;
}
}
ToArray
它将从列表中创建一个数组。以下程序将展示 ToArray()
方法的用法。方法签名如下:
public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
ToList<TSource>
具有类似的行为,但返回的是 List<T>
而不是数组。
阅读 更多。
让我们看一个 ToArray()
的例子:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> firstList = new List<int>()
{
1,2,3,4
};
var result = firstList.ToArray();
result.ToList().ForEach(x => Console.WriteLine(x));
}
}
}
此程序将产生以下输出:
1
2
3
4
CLR 将按如下方式执行上述操作:
第一步:CLR 将原始列表作为输入传递给 ToArray<TSource>(this IEnumerable<TSource> source)
方法。在 ToArray
方法内部,它将通过将原始列表对象作为输入来创建一个 Buffer<TSource>
类型的实例。
第二步:CLR 将从原始列表中逐个项目复制到名为 items 的内部数组中。ToArray()
方法的近似代码如下:
public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
{
Buffer<TSource> buffer = new Buffer<TSource>(source);
return buffer.ToArray();
}
Buffer<TSource>
结构体的内部如下:
internal struct Buffer<TElement>
{
internal TElement[] items;
internal int count;
internal Buffer(IEnumerable<TElement> source)
{
TElement[] array = null;
int length = 0;
ICollection<TElement> i.= source as ICollection<TElement>;
if (i.!= null)
{
length = i..Count;
if (length > 0)
{
array = new TElement[length];
i..CopyTo(array, 0);
}
}
else
{
foreach (TElement local in source)
{
if (array == null)
{
array = new TElement[4];
}
else if (array.Length == length)
{
TElement[] destinationArray = new TElement[length * 2];
Array.Copy(array, 0, destinationArray, 0, length);
array = destinationArray;
}
array[length] = local;
length++;
}
}
this.items = array;
this.count = length;
}
}
第三步:最后,当 CLR 调用 ToArray
方法时,它将返回
internal TElement[] ToArray()
{
if (this.count == 0)
{
return new TElement[0];
}
if (this.items.Length == this.count)
{
return this.items;
}
TElement[] destinationArray = new TElement[this.count];
Array.Copy(this.items, 0, destinationArray, 0, this.count);
return destinationArray;
}
ToArray
方法的输出为一个 items 数组的副本。
ToDictionary
它从 IEnumerable<T>
创建一个 Dictionary<tkey,>。如果我们想基于列表数据创建字典对象,此方法将自行完成所有操作,但我们需要指定列表数据中的一个字段作为键。
ToDictionary
扩展的签名如下:
public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector)
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer)
阅读 更多。
让我们看一个易于理解 ToDictionary
方法的例子:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<Person> persons = new List<Person>()
{
new Person(){ Name="Person A", Address= "Address of A", Id= 111111},
new Person(){ Name="Person B", Address= "Address of B", Id= 111112},
new Person(){ Name="Person C", Address= "Address of C", Id= 111113},
new Person(){ Name="Person D", Address= "Address of D", Id= 111114},
};
var result = persons.ToDictionary(person => person.Id);
foreach (KeyValuePair<double, Person> person in result)
{
Console.WriteLine("{0,-15} {1,-20}{2,-20}{3,-20}",
person.Key,
person.Value.Name,
person.Value.Address,
person.Value.Id);
}
}
}
public class Person
{
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
public double Id
{
get;
set;
}
}
}
上述程序将产生以下输出:
111111 Person A Address of A 111111
111112 Person B Address of B 111112
111113 Person C Address of C 111113
111114 Person D Address of D 111114
我创建了一个 Person 对象列表并将其存储在一个 List 对象 persons 中。然后,我使用 ToDictionary 扩展方法将 persons 列表转换为 Dictionary。正如我们所见,ToDictionary 接受一个匿名方法作为输入。这个匿名方法实际上是一个键选择器,它将从列表中选择对象的键并将其设置为字典对象中的键。因此,从 Person 对象中,Id 将被选为结果字典的键,值将是 person 对象本身。有趣的是,Id 属性将被用作 Dictionary 的 Key,并且它也会存储在 person 对象中,就像它被初始化一样。
当 CLR 执行 ToDictionary 方法时,它会执行以下操作:
第一步:如果我们使用 C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll 程序集中的 System.Linq.Enumerable 命名空间,通过 ILDasm.exe 程序,我们可以看到 ToDictionary 方法内部调用了内部 ToDictionary 方法,该方法具有以下签名:
public static Dictionary<tkey,> ToDictionary<tsource,>( this IEnumerable<tsource> source,
Func<tsource,> keySelector, Func<tsource,> elementSelector, IEqualityComparer<tkey> comparer)
在 CLR 从 ToDictionary 扩展方法调用上述内部 ToDictionary
方法之前,它将创建一个元素选择器函数。在这种情况下,虽然我没有提供任何元素选择器,但 CLR 将使用默认元素选择器,即 IdentityFunction<TSource>.Instance
。
注意:IdentityFunction<TSource>.Instance
是一个内部类,它将用作 ToDictionary
方法的元素选择器。以下图显示了 System.Core.dll 程序集中的此类。
public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
return source.ToDictionary<TSource, TKey, TSource>(
keySelector, IdentityFunction<TSource>.Instance, null);
}
第二步:当 CLR 进入上述方法时,该方法会调用重载的 ToDictionary
,如下所示:
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> comparer)
{
Dictionary<TKey, TElement> dictionary = new Dictionary<TKey, TElement>(comparer);
foreach (TSource local in source)
{
dictionary.Add(keySelector(local), elementSelector(local));
}
return dictionary;
}
这将实例化一个 Dictionary<TKey, TElement>
类的实例。然后它将遍历原始列表,每个遍历的值都将传递给 KeySelector
和 ElementSelector
函数,以从遍历值中提取 Key 和 Value。
由于我提供了 (person => person.Id)
作为 KeySelector
,编译器将生成一个匿名方法 <Main>b_5
并将其传递给 KeySelector
,后者将从 person 对象返回 Id。编译器将提供 ElementSelector
( x=>x
,如 IdentityFunction<telement>
中,其中 x=>x
将被转换为
),它将返回值本身,即包含 Name、Address 和 Id 值的 person 对象。
ToList
它从 IEnumerable<T>
创建一个 List<T>
。阅读 更多。
此扩展方法的签名如下:
public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
为了解释 ToList()
扩展方法的工作细节,让我们看一个例子,即 ToList()
生成结果:
我写了一个小程序来展示 ToList( this IEnumerable<TSource> collection)
扩展方法的使用:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int>()
{
1,2,3,4,5,6,7,8,9,10
};
var result = numbers.Where(x => x > 3).ToList();
result.ForEach(x => Console.Write("{0}\t", x));
Console.WriteLine();
}
}
}
上面的程序将产生以下输出作为结果:
4 5 6 7 8 9 10
因此,CLR 将按如下方式执行 ToList()
:
第一步:ToList()
扩展方法将接受一个 IEnumerable
IEnumerable
对象作为输入传递给 List<TSource>
类型。代码的近似如下:
public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
return new List<TSource>(source);
}
第二步:ListIEnumerable<TSource>
集合作为输入。在构造函数内部,CLR 将使用 TSource 作为类型初始化 _items
数组,并将数组的初始大小定义为 4。然后它将遍历输入列表对象的枚举器。List 构造函数的近似代码如下:
public List(IEnumerable<T> collection)
{
ICollection<T> i. = collection as ICollection<T>;
if (i. != null)
{
int count = i..Count;
this._items = new T[count];
i..CopyTo(this._items, 0);
this._size = count;
}
else
{
this._size = 0;
this._items = new T[4];
using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
this.Add(enumerator.Current);
}
}
}
}
第三步:在迭代阶段,CLR 将检索到的每个项目都传递给 Add( TSource item)
方法,以将其添加到(在 第二步 中)初始化的 _items
数组中。Add 方法的近似代码如下:
public void Add(T item)
{
if (this._size == this._items.Length)
{
this.EnsureCapacity(this._size + 1);
}
this._items[this._size++] = item;
this._version++;
}
在 Add 方法中,最重要的代码是 this.EnsureCapacity(this._size + 1)
。此 _items
数组的大小是动态的,将由 EnsureCapacity
方法确保。
第四步:因此,在完成迭代后,CLR 将列表对象作为 ToList()
方法的输出返回,该列表对象将在 _items 数组中包含从给定的 IEnumerable<TSource>
对象返回的元素。
Zip
它将指定的函数应用于两个序列的相应元素,生成一个结果序列。该方法逐步遍历两个输入序列,将函数 resultSelector
应用于两个序列的相应元素。该方法返回由 resultSelector
返回的值序列。如果输入序列的元素数量不相同,则该方法将组合元素,直到到达其中一个序列的末尾。例如,如果一个序列有三个元素而另一个有四个,则结果序列只有三个元素。Zip 扩展方法将根据提供的组合逻辑逐个项地组合两个列表项。根据以下方法签名,我们可以看到它是 IEnumerable<TFirst>
类型的扩展,并接受 IEnumerable<TSecond> second
、Func<TFirst, TSecond, TResult> resultSelector
项目作为输入。
此扩展方法的签名如下:
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first, IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
阅读 更多。
因此,第一个和第二个列表的项将逐个组合在一起,以基于 resultSelector Func
中提供的组合逻辑生成一个新列表。因此,我们可以看到 Zip 方法将组合列表中的每个项目,如下所示:
基于以上图示,我写了一个小程序,它将 firstList
(包含 {1, 2, 3, 4} 项目)与 secondList
(包含 {"One","Two","Three","Four"})结合起来,组合逻辑是:第一个列表的项目 + “:\t” + 第二个列表的项目。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chapter_5
{
class Program
{
static void Main(string[] args)
{
IList<int> firstList = new List<int>()
{
1,2,3,4
};
IList<string> secondList = new List<string>()
{
"One","Two","Three","Four"
};
var result = firstList.Zip(secondList, (x, y) => x + ":\t" + y);
result.ToList().ForEach(x => Console.WriteLine(x));
}
}
}
此程序将产生以下输出作为结果:
1: One
2: Two
3: Three
4: Four
因此,当编译器找到 Zip
方法时,它将:
第一步:编译器将使用匿名方法 (x, y) => x + ":\t" + y
构造一个方法 <Main>b_2
。CLR 将此 <Main>b_2
方法传递给 MulticastDelegate
类以实例化一个实例。
注意:如果我们反编译程序生成的已执行文件并将其放入 ILDasm.exe 程序,我们可以看到编译器为 (x, y) => x + ":\t" + y
代码生成了以下方法块:
.method private hidebysig static string '<Main>b__2'(int32 x,
string y) cil managed
{
// Code size 22 (0x16)
.maxstack 3
.locals init ([0] string CS$1$0000)
IL_0000: ldarg.0
IL_0001: box [mscorlib]System.Int32
IL_0006: ldstr ":\t"
IL_000b: ldarg.1
IL_000c: call string [mscorlib]System.String::Concat(object,
object,
object)
IL_0011: stloc.0
IL_0012: br.s IL_0014
IL_0014: ldloc.0
IL_0015: ret
} // end of method Program::'<Main>b__2'
第二步:CLR 将在步骤 1 中创建的 MulticastDelegate
实例传递给 Zip 方法,该方法将在执行一些基本空值检查后返回 ZipIterator
实例。ZipIterator
实例将保存第一个和第二个列表以及 resultSelector
(步骤 1 中创建的 MulticastDelegate
实例)。
第三步:由于此 Zip 扩展方法将使用延迟执行模式执行,因此每当 CLR 执行 ToList()
方法时,它将遍历 ZipIterator
枚举器。在 ZipIteraor
枚举器内部,CLR 将遍历每个列表并获取每个列表的当前项,然后将该当前项作为输入传递给 resultSelector
Func。然后 resultSelector
将每个提供的项组合成一个单一的项(例如,firstList
中的 1 和 secondList
中的 One 将组合成 1: One)并返回。这将继续进行,直到两个列表都完成。在此迭代过程中,如果一个列表的项少于另一个列表,它将仅从两个列表中返回相同数量的项。例如,如果列表 A 包含 {A1,B1,C1,D1} 项目而列表 B 包含 {A2,B2,C2},则结果将基于组合逻辑 (+) 处理的结果 {A1A2, B1B2,C1C2}。列表 A 中的 D1 将被丢弃。
Zip 扩展的近似代码如下:
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
return ZipIterator<TFirst, TSecond, TResult>(first, second, resultSelector);
}
private static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>(
IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> iteratorVariable0 = first.GetEnumerator())
{
using (IEnumerator<TSecond> iteratorVariable1 = second.GetEnumerator())
{
while (iteratorVariable0.MoveNext() && iteratorVariable1.MoveNext())
{
yield return resultSelector(
iteratorVariable0.Current,
iteratorVariable1.Current);
}
}
}
}
相关文章
历史
- 添加了扩展方法在后台的工作细节。
- PDF 文件已更新。
- 目录已更新。
- 为 Min 扩展添加了外部链接。
- 目录已更新。
- PDF 文件已删除。