本文最后更新于:2024年8月6日 中午
                  
                
              
            
            
              
              委托(Delegate)
委托是什么?
委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。——C#编程指南
用我的话来说,就是可以按顺序执行一系列方法的对象。
比如:现在有两个类,一个“肉铺”类,一个“蔬菜店”类,这两个类里边各自拥有名为“取得肉”和“取得蔬菜”的方法。现在你妈让你去“取得肉”和“取得蔬菜”,也就是调用这两个方法,我们可以怎么办呢?
最直接的办法就是直接调用,那要是你妈天天都有这个需求怎么办呢?我们或许会新创建一个方法,并把这两个方法放入这个方法的方法体中,然后调用这个新方法。当然,我们也能够创建一个委托,并把这两个方法交给委托,然后执行委托。
委托的食用方法
最基本的食用方法
首先我们需要使用delegate关键字来创建委托类型,然后实例化委托,最后调用委托。上面例子的完整代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | namespace Learning1{
 internal class Program
 {
 
 public delegate void Del(string name);
 static void Main(string[] args)
 {
 string myName = "Maple";
 
 Del myDel = ButcherShop.GetMeet;
 
 myDel += Greengrocer.GetVegetables;
 
 myDel(myName);
 }
 }
 
 
 public class ButcherShop
 {
 public static void GetMeet(String name)
 {
 Console.WriteLine($"Congratulations!{name} get meet!");
 }
 }
 
 
 public class Greengrocer
 {
 public static void GetVegetables(String name)
 {
 Console.WriteLine($"Congratulations!{name} get vegetables!");
 }
 }
 }
 
 | 
执行成功后,显示如下:
| 12
 3
 4
 5
 6
 
 | Congratulations!Maple get meet!Congratulations!Maple get vegetables!
 
 E:\Project\C#\CSharpLearning\Learning1\Learning1\bin\Debug\net6.0\Learning1.exe (进程 30208)已退出,代码为 0。
 要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
 按任意键关闭此窗口. . .
 
 | 
委托作为实参或分配给一个属性
因为实例化的委托也是一个对象,所以可以将其作为实参传入方法中,又或者将其分配给一个属性。
这允许方法接受委托作为参数并在稍后调用委托。 这被称为异步回调,是在长进程完成时通知调用方的常用方法。——C#编程指南
依旧是上面的例子,不过这次要和朋友去买菜了,所以创建一个新方法BuyWithFriend,该方法接受三个参数,前两个是买菜人的名字,最后一个参数是一个委托。代码如下:
| 12
 3
 4
 
 | static void BuyWithFriend(string name1, string name2, Del del){
 del(name1 + " and " + name2);
 }
 
 | 
然后在主程序使用BuyWithFriend(myName, "Tom", myDel)来调用新方法,得到如下输出:
| 12
 3
 4
 5
 6
 
 | Congratulations!Maple and Tom get meet!Congratulations!Maple and Tom get vegetables!
 
 E:\Project\C#\CSharpLearning\Learning1\Learning1\bin\Debug\net6.0\Learning1.exe (进程 21864)已退出,代码为 0。
 要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
 按任意键关闭此窗口. . .
 
 | 
委托也是派生类
委托类型派生自System.Delegate,而且是密封的(sealed)。我们可以在委托上调用Delegate类定义的方法和属性,比如查询委托调用列表中方法的数量:Console.WriteLine(myDel.GetInvocationList().GetLength(0));。
调用列表中具有多个方法的委托派生自MulticastDelegate,该类属于System.Delegate的子类。
协变和逆变
其实这一块应该放在泛型里面说的(?)。
所谓协变,就是定义的委托类型的返回类型,接受其派生类。如下代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 
 | internal class CovarianceAndContravariance{
 delegate T Factory<out T>();
 
 static Dog MakeDog()
 {
 return new Dog();
 }
 
 static void Main(string[] args)
 {
 
 Factory<Dog> dogMaker = MakeDog;
 Factory<Animal> animalMaker = dogMaker;
 
 Console.WriteLine(animalMaker().Legs.ToString());
 }
 }
 
 public class Animal
 {
 public int Legs = 4;
 }
 
 public class Dog : Animal
 {
 
 }
 
 | 
在这里,如果把out关键字去掉的话,编译是不通过的,这是因为Factory<Dog>并不是从Factory<Animal>派生的。
所以这里的out关键字标记委托声明中的类型参数,可以让编译器知道在这里类型参数只用作于输出值。这就是协变。
那逆变其实和协变差不多,就是在期望传入基类时允许传入派生对象的特性,就叫逆变。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 
 | internal class CovarianceAndContravariance{
 delegate void Action1<in T>(T a);
 
 static void ActOnAnimal(Animal a)
 {
 Console.WriteLine(a.Legs);
 }
 
 static void Main(string[] args)
 {
 
 Action1<Animal> act1 = ActOnAnimal;
 Action1<Dog> dog1 = act1;
 
 dog1(new Dog());
 }
 }
 
 public class Animal
 {
 public int Legs = 4;
 }
 
 public class Dog : Animal
 {
 
 }
 
 | 
有关协变和逆变的理解这块我个人可能不是很透彻,建议参考参考其他文章。
强委托类型
其实就是.Net Core框架里边的泛型委托类型,包括Action、Func和Predicate。
其中Action类型用于任何具有void返回类型的委托类型:
| 12
 3
 4
 
 | public delegate void Action();public delegate void Action<in T>(T arg);
 public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
 
 
 | 
Func类型用于具有返回值的委托类型:
| 12
 3
 4
 
 | public delegate TResult Func<out TResult>();public delegate TResult Func<in T1, out TResult>(T1 arg);
 public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
 
 
 | 
Predicate则用于返回单个值的测试结果:
| 1
 | public delegate bool Predicate<in T>(T obj);
 | 
当然你也许已经注意到了,Action就是逆变的应用,Func就是协变的应用。
委托的常见模式
LINQ查询表达式
LINQ查询表达式模式依赖于其所有功能的委托,比如Where方法的原型:
| 1
 | public static IEnumerable<TSource> Where<TSource> (this IEnumerable<TSource> source, Func<TSource, bool> predicate);
 | 
通过委托生成自己的组件
下面定义了一个可用于大型系统中日志消息的组件。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 
 | public enum Severity{
 Verbose,
 Trace,
 Information,
 Warning,
 Error,
 Critical
 }
 
 public static class Logger
 {
 public static Action<string>? WriteMessage;
 
 public static Severity LogLevel { get; set; } = Severity.Warning;
 
 public static void LogMessage(Severity severity, string component, string message)
 {
 if (severity < LogLevel)
 return;
 
 var outputMsg = $"{DateTime.Now}\t{severity}\t{component}\t{message}";
 WriteMessage?.Invoke(outputMsg);
 }
 }
 
 public static class LoggingMethod
 {
 public static void LogToConsole(string message)
 {
 Console.Error.WriteLine(message);
 }
 }
 
 public class FileLogger
 {
 private readonly string _logPath;
 public FileLogger(string logPath)
 {
 _logPath = logPath;
 Logger.WriteMessage += LogMessage;
 }
 
 public void DetachLog() => Logger.WriteMessage -= LogMessage;
 private void LogMessage(string message)
 {
 try
 {
 using (var log = File.AppendText(_logPath))
 {
 log.WriteLine(message);
 log.Flush();
 }
 }
 catch (Exception)
 {
 
 }
 }
 }
 
 | 
Logger的使用示例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public static void Main(){
 FileLogger logger = new FileLogger(@".\log.txt");
 try
 {
 throw new Exception($"Logger Test: {new Random().Next(100)}");
 }
 catch (Exception ex)
 {
 Logger.LogMessage(Severity.Error, "LoggerTest.Main", ex.Message);
 LoggingMethod.LogToConsole(ex.Message);
 }
 }
 
 | 
大致工作流程:首先,new一个FileLogger对象,指定要生成的日志的路径及名称,在FileLogger的构造方法中,会自动将自己的私有LogMessage方法挂载到Logger的静态委托WriteMessage上;
然后,在需要写入日志的地方(如捕获到异常)直接使用Logger.LogMessage,该方法中会执行委托的Invoke方法,开始处理委托。
事件(Event)
什么是事件?
类或对象可以通过事件向其他类或对象通知发生的相关事情。 发送(或引发)事件的类称为“发布者”,接收(或处理)事件的类称为“订阅者”。——C#编程指南
事件和委托类似,也是后期绑定机制(实际上,事件是建立在对委托的语言支持之上的)。当然要我说的话,无非就是用来实现发布订阅模式的东西,比如现在我们有一个事件,发布者会发布这个事件,广播这个事件发生了,而广播的对象就是那些订阅了这个事件的订阅者。其实就是图形系统的交互啦,点击一个按钮,然后会发生一些事情,其实就是事件的应用。
事件食用方法
定义事件:public event EventHandler<FileListArgs> Progress;
在这里,EventHandler是委托类型,而Progress是名称。
            事件不是类型!和方法、属性一样,事件是类或结构的成员!
           
引发事件:Progress?.Invoke(this, new FileListArgs(file));
订阅事件:
| 12
 3
 4
 
 | EventHandler<FileListArgs> onProgress = (sender, eventArgs) =>Console.WriteLine(eventArgs.FoundFile);
 
 fileLister.Progress += onProgress;
 
 | 
取消订阅:fileLister.Progress -= onProgress;
举个例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 
 | class SimpleEventArgs : EventArgs{
 public string Msg { get; }
 public SimpleEventArgs(string msg) => Msg = msg;
 }
 
 class Publisher
 {
 public event EventHandler<SimpleEventArgs>? SimpleEvent;
 public void RaiseTheEvent(string msg) =>
 SimpleEvent?.Invoke(this, new SimpleEventArgs(msg));
 }
 
 class Subscriber
 {
 public EventHandler<SimpleEventArgs> onA = (sender, eventArgs) => { Console.WriteLine($"A: {eventArgs.Msg}"); };
 public EventHandler<SimpleEventArgs> onB = (sender, eventArgs) => { Console.WriteLine($"B: {eventArgs.Msg}"); };
 }
 
 internal class Event
 {
 public static void Main(string[] args)
 {
 Publisher publisher = new Publisher();
 Subscriber subscriber = new Subscriber();
 
 publisher.SimpleEvent += subscriber.onA;
 publisher.SimpleEvent += subscriber.onB;
 publisher.RaiseTheEvent("test");
 
 Console.WriteLine("\r\nRemove onB");
 publisher.SimpleEvent -= subscriber.onB;
 publisher.RaiseTheEvent("test 2");
 }
 }
 
 | 
运行后,控制台输出如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | A: testB: test
 
 Remove onB
 A: test 2
 
 E:\Project\C
 要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
 按任意键关闭此窗口. . .
 
 | 
然后大致说明一下事件的一个流程:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | Publisher publisher = new Publisher();Subscriber subscriber = new Subscriber();
 
 publisher.SimpleEvent += subscriber.onA;
 publisher.SimpleEvent += subscriber.onB;
 publisher.RaiseTheEvent("test");
 
 Console.WriteLine("\r\nRemove onB");
 publisher.SimpleEvent -= subscriber.onB;
 publisher.RaiseTheEvent("test 2");
 
 | 
首先使用+=订阅事件,也就是给事件增加事件处理程序,这里决定之后发布事件的时候会调用哪些事件处理程序进行处理。然后调用RaiseTheEvent方法发布(触发)事件,该方法包含了SimpleEvent?.Invoke()语句。之后事件被触发,执行该事件保存的事件处理程序。
            EventArgs不能传递任何数据。如果要传递数据,那就声明一个派生自EventArgs的类(比如上面的SimpleEventArgs类)。
           
我们还可以改变+=和-=运算符的行为,只需要为事件定义事件访问器即可:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | public event EventHandler<SimpleEventArgs>? SimpleEvent{
 add
 {
 ...
 }
 remove
 {
 ...
 }
 }
 
 | 
泛型(Generic)
泛型是什么
泛型(generic)允许我们声明类型参数化(type-parameterized)的代码,用不同的类型进行实例化。也就是,我们可以用“类型占位符”来写代码,然后在创建类的实例时指名真实的类型。
简单来说就是现在我们有一个返回两数中更大数的方法,返回类型可以不再限定是某一个具体的类型(比如int),而是可以是任一类型,这样我们的代码就能够进行复用。
泛型的食用方法
泛型方法
首先声明泛型方法:
| 12
 3
 4
 
 | public void OneGenericMethod<TFirst, TSecond>(TFirst first, TSecond second){
 ...
 }
 
 | 
需要调用这个泛型方法的时候,用实际类型将类型参数列表的泛型类型替代即可:
| 1
 | OneGenericMethod<int, int>(0, 0);
 | 
当然编译器能够从方法参数中推断类型参数,所以我们可以省略尖括号:
泛型类
要创建泛型类,一共分为三大步:
- 声明泛型类
- 构造实际类
- 创建实例
首先是声明泛型类:
| 12
 3
 4
 5
 
 | class OneGenericClass<TFirst, TSecond>{
 public TFirst First;
 public TSecond Second;
 }
 
 | 
然后是构造实际类和使用new关键字创建实际类的实例:
| 1
 | public OneGenericClass<int, int> A = new OneGenericClass<int, int>();
 | 
类型参数的约束
我们可以提供额外信息来让编译器知道参数可以接受哪些类型,这些额外信息就是约束。只有符合约束的类型才能替代给定的类型参数来产生构造类型。
利用Where子句来对泛型接受的类型进行约束。
约束有非常多种,建议直接在官网看:点我
这里列举几个常用的约束:
- where T : class限定类型参数必须是引用类型
- where T : <基类名>限定类型参数必须是指定的基类或派生自指定的基类
- where T : <接口名称>限定类型参数必须是指定的接口或实现指定的接口
泛型结构
泛型结构和泛型类相似,这里不再赘述。
泛型委托
其实在上面已经见过了,C#本身提供了几个泛型委托。
泛型接口
泛型接口其实也跟泛型类差不多,毕竟泛型接口就是为了给泛型类实现的。
与其他泛型相似,用不同类型参数实例化的泛型接口的实例时不同接口,而且我们也可以在非泛型类型中实现泛型接口,比如:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | interface IExampleIfc<T>{
 T ReturnValue(T value);
 }
 
 class ExampleClass : IExampleIfc<int>, IExampleIfc<string>
 {
 public int ReturnValue(int value)
 {
 return value;
 }
 
 public string ReturnValue(string value)
 {
 return value;
 }
 }
 
 | 
不过需要注意的是下面这种情况:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | interface IExampleIfc<T>{
 T ReturnValue(T value);
 }
 
 class ExampleClass<S> : IExampleIfc<int>, IExampleIfc<S>
 {
 public int ReturnValue(int value)
 {
 return value;
 }
 
 public S ReturnValue(S value)
 {
 return value;
 }
 }
 
 | 
如果这里的S利用int来替代的话,这个类就有两个相同类型的接口了,而这是不允许的。
迭代器(Iterator)
如果我们要循环访问一个集合中的所有内容,我们就会使用foreach语句。那要在这个集合上使用foreach语句,前提就是这个集合会提供一个叫做**枚举器(enumerator)**的对象。(其实这里不就像python中的for index, item in enumerator(list)吗)
那么如何让集合提供一个枚举器呢?那就是通过IEnumerable<T>和IEnumerator<T>这两个接口(当然C#也提供了这两个接口的非泛型版本)。
迭代器会为我们创建枚举器。
(其实这部分讲得不是很清楚,自己理解的也不是很清楚,建议还是看看官网吧:点我
枚举器的食用方法
对于一个方法,可以简单的将其返回类型设置为IEnumerable并在方法体内使用yield return语句来实现返回迭代器。
IEnumerable是产生可枚举类型的迭代器,而IEnumerator是产生枚举器的迭代器。
对于一个类,有两种模式。
- 枚举器的迭代器模式
- 可枚举类型的迭代器模式
其中第一种模式,是在类里面利用IEnumerator首先实现一个返回枚举器的迭代器,然后再通过实现GetEnumerator来让类可枚举。代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | class MyClass{
 public IEnumerator GetEnumerator()
 {
 return IteratorMethod();
 }
 
 public IEnumerator IteratorMethod()
 {
 yield return ...;
 }
 }
 
 Main
 {
 MyClass mc = new MyClass();
 foreach( string x in mc )
 ...
 }
 
 | 
而第二种模式则是在类里用IEnumerable来实现返回可枚举类型的迭代器,这样做的话,我们可以不需要实现GetEnumerator,因为能够直接调用迭代器方法来获取可枚举类型。代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | class MyClass{
 public IEnumerator GetEnumerator()
 {
 return IteratorMethod().GetEnumerator();
 }
 
 public IEnumerable IteratorMethod()
 {
 yield return ...;
 }
 }
 
 Main
 {
 MyClass mc = new MyClass();
 foreach( string x in mc )
 ...
 foreach( string x in mc.IteratorMethod() )
 ...
 }
 
 | 
语言集成查询(LINQ)
什么事LINQ
LINQ,即语言集成查询(Language Integrated Query),是 .NET 框架的扩展,它允许我们以使用 SQL 查询数据库的类似方式来查询数据集合。使用 LINQ,我们可以从数据库、对象集合以及 XML 文档等中查询数据。
查询操作的三个部分
所有 LINQ 查询操作都由以下三个不同的操作组成:
- 获取数据源,如:int[] nums = { 0, 1, 2, 3, 4 };
- 创建查询,如:IEnumerable<int> numQuery = from num in nums where (num % 2) == 0 select num;
- 执行查询,如:foreach (int num in num Query)	Console.WriteLine(num);
数据源
LINQ 数据源是支持泛型IEnumerable<T>接口或从中继承的接口的任意对象。
查询
查询是一组指令。查询指定要从数据源中检索的信息。查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化。
目前需要注意的是,在 LINQ 中,查询变量本身不执行任何操作并且不返回任何数据,它只是存储在以后某个时刻执行查询时为生成结果而必需的信息。
执行查询
查询变量本身只存储查询命令,以前面的代码为例,numQuery就是查询变量,但是它不包含查询的结果,而是包含能够执行这个查询的代码。但是如果查询返回的是标量(即单个值,如:numQuery.Count()),则查询会立即执行,并且把结果保存在查询变量中。
- 如果查询表达式返回枚举,则查询一直到处理枚举时才会执行。
- 如果枚举被处理多次,查询就会执行多次。
- 如果在进行遍历之后、查询执行之前数据有改动,则查询会使用新的数据。
- 如果查询表达式返回标量,查询立即执行,并且把结果保存在查询变量中。
方法语法和查询语法
我们在写 LINQ 查询时可以使用两种形式的语法:查询语法和方法语法。
- 方法语法(method syntax):使用标准的方法调用。方法语法时**命令式(imperative)**的,它指明了查询方法调用的顺序。
- 查询语法(query syntax):和 SQL 语句很相似,使用查询表达式形式书写。查询语法是**声明式(declarative)**的,也就是说,查询描述的是你想返回的东西,但并没有指明如何执行这个查询。
- 方法语法 + 查询语法混用
            微软推荐使用查询语法。不过有一些运算符必须使用方法语法来书写。
           
查询表达式
查询表达式是以查询语法表示的查询。查询表达式必须以from子句开头,且必须以select或group子句结尾。
from 子句
from子句指定了要作为数据源使用的数据集合。from子句的用法如下:from Type Item in Items
- Type是集合中元素的类型。这是可选的,因为编译器可以从集合中推断类型。
- Item是迭代变量的名字。
- Items是要查询的集合的名字。集合必须是可枚举的(IEnumerable)。
join 子句
join子句可基于每个元素中指定的键之间的相等比较,将一个数据源中的元素与另一个数据源中的元素进行关联和/或合并。例如:
| 12
 3
 
 | var query = from s in students join c in stuInCourses on s.StId equals c.StId
 select s.Name;
 
 | 
注意必须使用上下文关键字equals来比较字段,而不能使用==运算符。
where 子句
where子句可基于一个或多个谓词表达式,从数据源中筛选出元素。例如:
| 12
 3
 
 | IEnumerable<Student> query = from s in students where s.Age = 18
 select s;
 
 | 
let 子句
let子句可将表达式(如方法调用)的结果存储在新范围变量中。例如:
| 12
 3
 
 | IEnumerable<string> query = from name in names let firstName = name.Split(' ')[0]
 select firstName;
 
 | 
orderby 子句
orderby子句可按升序或降序对结果进行排序,还可以指定次要排序顺序。例如:
| 12
 3
 
 | IEnumerable<Student> query = from s in students orderby s.StId, s.Age descending
 select s;
 
 | 
select 子句
select子句可生成所有其他类型的序列。比如:
| 12
 3
 
 | IEnumerable<Country> sortedQuery = from country in countries orderby country.Area
 select country;
 
 | 
在这里select子句生成重新排序的Country对象的序列。
group 子句
group子句可生成按指定键组织的组的序列。不过要注意的是group子句返回对象集合的集合(IEnumerable<IGrouping<TKey, TElement>>),而不是对象的集合。
into 子句
into子句可以接受查询的一部分的结果并赋予一个名字,从而可以在查询的另一个部分中使用。例如:
| 12
 3
 4
 5
 
 | var query = from a in groupAjoin b in groupB on a equals b
 into groupAandB
 from c in groupAandB
 select c
 
 | 
标准查询运算符
标准查询运算符由一系列 API 方法组成,能让我们查询任何 .NET 数组和集合。例如以下两个查询是等效的:
| 12
 3
 4
 5
 6
 
 | IEnumerable<int> qurey1 = from num in numberswhere num % 2 == 0
 orderby num
 select num;
 
 IEnumerable<int> query2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);
 
 | 
如果想要了解更多 API 请参考官方文档:标准查询运算符概述 (C#)
扩展:PLINQ
PLINQ 就是 LINQ 的并行实现,它会尝试充分利用系统上的所有处理器(来执行查询从而提升性能)。如果单独处理源集合中的每个元素,且各个代理之间不涉及共享状态,PLINQ 的性能最佳。
在许多情况下,并行执行意味着查询运行速度显著提高。…但是,并行可能会引入其自身的复杂性,因此并非所有的查询操作的运行速度在 PLINQ 中都更快。 事实上,并行实际上会降低某些查询的速度。 因此,应了解排序等问题将如何对并行查询产生影响。
System.Linq.ParallelEnumerable类及其常用方法
- AsParallel: PLINQ 的入口点。指定如果可能,应并行化查询的其余部分。
- AsSequential: 指定查询的其余部分应像非并行的 LINQ查询一样按顺序运行。
- AsOrdered: 指定 PLINQ 应为查询的其余部分保留源序列的排序
- WithCancellation: 指定 PLINQ 应定期监视请求取消时所提供的取消标记的状态以及取消执行。
执行模式
可以使用WithExecutionMode方法和System.Linq.ParallelExecutionMode枚举指示 PLINQ 选择并行算法。
并行度
默认情况下,PLINQ 使用主机计算机上的所有处理器。可以使用WithDegreeOfParallelism方法指示 PLINQ 使用不超过指定数量的处理器。
ForAll运算符
在 PLINQ 中,如果不在乎查询结果的最终排序,请使用ForAll方法执行 PLINQ 查询。(PLINQ 查询会将从每个工作线程中得到的结果合并回主线程上,但是ForAll不执行最终的这一合并步骤)
              
希望本文章能够帮到您~