C#进阶C# 匿名方法

Posted 哈桑c

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#进阶C# 匿名方法相关的知识,希望对你有一定的参考价值。

序号系列文章
18【C#进阶】C# 事件
19【C#进阶】C# 集合类
20【C#进阶】C# 泛型

文章目录

前言

📺 hello大家好啊,我是哈桑c,本文为大家介绍 C# 中的匿名方法。


1、什么是匿名方法?

匿名方法顾名思义就是这类方法的特点是不需要特别去定义函数的名字的。一般我们需要一个函数,但又不想花时间去命名它的时候,就可以使用匿名方法。在 C# 中, 匿名方法通常表现为使用 delegate 运算符和 Lambda 表达式。

代码示例:(简单演示)

delegate 运算符:

public class SampleAnonymousMethod

    static void Main(string[] args)
    
        Func<int, int, int> sum = delegate (int a, int b)  return a + b; ;
        Console.WriteLine(sum(11,22));  
        
        //输出:33
    

Lambda 表达式:

public class SampleAnonymousMethod

    static void Main(string[] args)
    
        Func<int, int> square = x => x * x;
        Console.WriteLine(square(5));
        
        // 输出:25
    

2、delegate 运算符

delegate 运算符创建一个可以转换为委托类型的匿名方法。 匿名方法可以转换为System.Action 和 System.Func<TResult> 等类型,同时指定自定义的参数列表。

代码示例:

Func<string, string, string> hello = delegate (string s1, string s2)  return s1 + s2; ;
Console.WriteLine(hello("hello,", "world"));  

运行结果:

可以看到,即使是一个没有方法名只有方法体的匿名方法也可以正常输出运算结果。

使用 delegate 运算符时,也可以直接省略参数列表,表示创建的匿名方法可以转换为具有任何参数列表的委托类型。

代码示例:

Action greet = delegate  Console.WriteLine("你好!"); ;
greet();

Action<int, double> introduce = delegate  Console.WriteLine("这就是世界!"); ;
introduce(7, 2.5);

运行结果:

在上例中可以看到,即可没有声明参数列表的匿名方法在转换为有参数列表的 Action 方法时也可以正常运行。

从 C# 9.0版本开始,可以使用弃元1指定该方法不使用的两个或更多个匿名方法的输入参数。当方法中返回的结果没有任何作用时,就可以使用弃元。

代码示例:

Func<int, int, int> constant = delegate (int _, int _)  return 42; ;
Console.WriteLine(constant(3, 4));  

// 输出:42

3、Lambda 表达式

Lambda 表达式的本质也是匿名方法。Lambda 表达式提供了一种简洁和富有表现力的方式来创建匿名函数,可以使用 => 运算符来构造 Lambda 表达式。

Lambda 表达式的定义语法可以总结为如下:

(input-parameters) =>  <sequence-of-statements> 
  • input-parameters: 表示输入参数,也即参数列表。
  • sequence-of-statements: 表示表达式语句序列部分。

若要创建 Lambda 表达式,需要在 Lambda 运算符左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块。当语句序列部分有且仅有一个表达式时,可以省略大括号的书写。

任何 Lambda 表达式都可以转换为对应的委托类型。 Lambda 表达式可以转换的委托类型由其参数和返回值的类型定义。如果 lambda 表达式不返回值,则可以将其转换为 Action 委托类型之一;否则,可将其转换为 Func 委托类型之一。

代码示例:

// lambda 表达式不返回值,可以将其转换为 Action 委托类型之一 
Action greet = () =>  Console.WriteLine("Hello,World"); ;   
greet();

// 输出:"Hello,World"
// lambda 表达式返回值,可以转换为 Func 委托类型之一 
Func<int, int> square = x => x * x;
Console.WriteLine(square(5));

// 输出:25

演示 Lambda 表达式有参和无参的两种写法。

有参写法:

Action<string> greet = name => Console.WriteLine($"Hello name!";
greet("World");

// 输出: Hello World!  

编写 lambda 时,通常不必为输入参数指定类型,因为编译器可以根据 lambda 主体、参数类型以及 C# 语言规范中描述的其他因素来推断类型。

无参写法:

Action greet = () => Console.WriteLine($"Hello World!");
greet(); 

// 输出: Hello World!  

3.1、Lambda 表达式的自然类型

从 C# 10开始,Lambda 表达式可能具有自然类型,也就是编译器可以根据 Lambda 表达式推断委托类型。但是并非所有 Lambda 表达式都有自然类型,有时候编译器无法推断 s 的参数类型。这时我们就必须声明 Lambda 表达式的类型了。

代码示例:

object parse = (string s) => int.Parse(s); 
Console.WriteLine(parse.GetType());

运行结果:

从上例中可以看到,编译器顺利推断出了匿名方法的类型 System.Func ,而无需声明具体的方法类型。

代码示例:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

代码效果:

3.2、Lambda 表达式的显示返回类型

从 C# 10 开始,使用 Lambda 表达式时可以在输入参数前面显示指定表达式的返回类型,同时必须使用括号把输入参数括起来。

代码示例:

var judge = int (bool b) => b ? 1 : 2;      // int表示返回类型为整数类型
Console.WriteLine(judge(true));

// 输出:1

4、关于匿名方法的总结

匿名方法的优点:

  • 简洁性: 匿名方法可以用更少的代码实现相同的功能。减少代码的复杂性和冗余性,使代码更加简洁。
  • 灵活性: 匿名方法可以作为参数传递给方法,也可以当作返回值返回。
  • 可读性: 匿名方法代码量更少,可以使代码更加清晰易懂,可读性较高。
  • 性能高: 使用 Lambda 表达式可以轻松创建包含异步处理的表达式和语句。

匿名方法的缺点:

  • 重用性低: 匿名方法不能在其它地方进行调用,因此重用性较低。

什么时候需要使用匿名方法:

  • 临时需要一个方法,而且方法使用次数较少。
  • 方法的代码量很少,使用匿名方法显得更加简洁。

点击了解更多匿名方法的使用。


结语

📱 以上就是 C# 匿名方法的介绍啦,希望对大家有所帮助。感谢大家的点赞、收藏、评论和关注。


  1. 弃元: 是一种在应用程序代码中人为取消使用的占位符变量。 ↩︎

C#进阶C# 多线程

序号系列文章
19【C#进阶】C# 集合类
20【C#进阶】C# 泛型
21【C#进阶】C# 匿名方法

文章目录

前言

🐪 hello大家好,我是哈桑c,本文为大家介绍 C# 中的多线程。


1、线程与多线程的基本概念

线程是操作系统能够进行运算调度的最小单位,它被包含在进程1之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流,一个进程可以并发2多个线程,每个线程并行执行不同的任务。

多线程是指在软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,进而提升整体处理性能的能力。

在 C# 中,实现多线程技术最常使用的类就是包含在 System.Threading 命名空间中的 Thread 类。Thread 类是一个定义创建和控制线程,设置其优先级并获取其状态的类。以一个简单的程序演示 Thread 类的用法,借此讨论多线程的使用。

代码示例:

using System;
using System.Threading;

// 简单的线程场景:启动一个静态方法运行在第二个线程上。
public class ThreadExample

    // 被运行的子线程
    public static void ThreadProc()
    
        for (int i = 0; i < 10; i++)
        
            Console.WriteLine("子线程: 0", i);
            Thread.Sleep(0);
        
    

    public static void Main()
    
        Console.WriteLine("主线程:开启第二个线程。");
        
        Thread t = new Thread(new ThreadStart(ThreadProc));
        t.Start();      // 开启子线程的工作
        
        // 同时开启主线程的工作
        for (int i = 0; i < 4; i++)
        
            Console.WriteLine("主线程:开始工作");
            Thread.Sleep(0);
        

        Console.WriteLine("主线程:调用Join(),等待子线程结束。");
        t.Join();
        Console.WriteLine("主线程:子线程已经 Join 回来了。按Enter键结束程序。");
        Console.ReadLine();
    

运行结果:

在上例中可以看出,使用 Thread 类我们不仅可以执行主线程3的内容,还可以创建新的 Thread 对象以此来运行子线程4的内容。这样我们就成功实现了一个多线程程序。

2、创建并使用线程

在 Thread 类中,创建子线程可以通过 Thread t = new Thread(); 调用构造方法的方式来实现。扩展的 Thread 类调用 Start() 方法来开始子线程的执行。

代码示例:

using System;
using System.Diagnostics;
using System.Threading;

public class Example

    public static void Main()
    
        var th = new Thread(ExecuteInForeground);
        th.Start(4500);
        Thread.Sleep(1000);
        Console.WriteLine("主线程 (0) 等待...",
                          Thread.CurrentThread.ManagedThreadId);
    

    private static void ExecuteInForeground(Object obj)
    
        int interval;
        try
        
            interval = (int)obj;
        
        catch (InvalidCastException)
        
            interval = 5000;
        

        var sw = Stopwatch.StartNew();
        Console.WriteLine("线程 0: 1, 属性 2",
                          Thread.CurrentThread.ManagedThreadId,
                          Thread.CurrentThread.ThreadState,
                          Thread.CurrentThread.Priority);
        do
        
            Console.WriteLine("线程 0: 运行 1:N2 描述",
                              Thread.CurrentThread.ManagedThreadId,
                              sw.ElapsedMilliseconds / 1000.0);
            Thread.Sleep(500);
         while (sw.ElapsedMilliseconds <= interval);
        sw.Stop();
    

运行结果:

在上例中,我们使用 var th = new Thread(ExecuteInForeground); 的方法并传入了 ExecuteInForeground 的构造方法名成功的创建了一个子线程

同时我们也可以使用 Thread 类中 start 方法来启动子线程的运行。注意如果方法需要有参数的话,那么这时就需要在 start 方法上传入参数,如果没有则反之。

3、检索线程对象

在使用 Thread 类时,如果想要获取线程的信息,可以从正在执行的代码中使用 Thread 类的属性来检索当前正在运行的线程的引用对象

代码示例:

using System;
using System.Threading;

public class ExampleThread

    // 创建一个obj对象便于用于锁机制
    static Object obj = new Object();

    public static void Main()
    
        ThreadPool.QueueUserWorkItem(ShowThreadInformation);    // 将方法排入队列以便执行。 
        var th1 = new Thread(ShowThreadInformation);
        th1.Start();
        var th2 = new Thread(ShowThreadInformation);
        th2.IsBackground = true;
        th2.Start();
        Thread.Sleep(500);
        ShowThreadInformation(null);
    

    private static void ShowThreadInformation(Object state)
    
        lock (obj)      // 为保证线程之间的执行顺序,在这里加上一个锁
        
            var th = Thread.CurrentThread;
            Console.WriteLine("托管线程 #0: ", th.ManagedThreadId);
            Console.WriteLine("后台线程: 0", th.IsBackground);
            Console.WriteLine("线程池线程: 0", th.IsThreadPoolThread);
            Console.WriteLine("优先级: 0", th.Priority);
            Console.WriteLine("文化: 0", th.CurrentCulture.Name);
            Console.WriteLine("UI文化: 0", th.CurrentUICulture.Name);
            Console.WriteLine();
        
    

运行结果:

从上例中可以看到,我们不仅创建了托管线程,还创建了前台线程、后台线程以及线程池的对象。在这里我们使用了 ManagedThreadId 等属性输出了线程的 Id 值、是否为后台线程或线程池5、优先级和线程名等线程信息。

4、前台线程和后台线程

在 .NET 框架的公用语言运行时(Common Language Runtime,CLR)中能区分两种不同类型的线程:前台线程和后台线程。

前台线程和后台线程的区别:

  • 如果所有前台线程已终止,后台线程不会使进程保持运行。
  • 停止所有前台线程后,运行时将停止所有后台线程并关闭。

前台线程和后台线程分别的应用范围。

默认情况下,以下线程在前台执行:

  • 主线程: 常见的主应用程序线程均为前台线程。
  • 子线程: 通过调用类构造函数创建 Thread 的所有线程。

默认情况下,以下线程在后台执行:

  • 线程池线程: 由运行时维护的工作线程池。 可以使用 类配置线程池并计划线程池线程 ThreadPool 上的工作。
  • 非托管到托管的线程: 从非托管代码进入托管执行环境的所有线程。

在 Thread 类中,可以通过设置 IsBackground 的属性来更改在后台执行的线程。后台线程适用于只要应用程序正在运行就应继续执行但不阻止应用程序终止的任何操作,例如监视文件系统更改或传入套接字连接。

代码示例:

using System;
using System.Diagnostics;
using System.Threading;

public class BackgroundThreadExample

    public static void Main()
    
        var th = new Thread(ExecuteInForeground);
        th.IsBackground = true;
        th.Start();
        Thread.Sleep(1000);
        Console.WriteLine("主线程 (0) 等待...", 
                        Thread.CurrentThread.ManagedThreadId); 
    

    private static void ExecuteInForeground()
    
        var sw = Stopwatch.StartNew();
        Console.WriteLine("线程 0: 1, 优先级 2",
                          Thread.CurrentThread.ManagedThreadId,
                          Thread.CurrentThread.ThreadState,
                          Thread.CurrentThread.Priority);
        do
        
            Console.WriteLine("Thread 0: Elapsed 1:N2 seconds",
                              Thread.CurrentThread.ManagedThreadId,
                              sw.ElapsedMilliseconds / 1000.0);
            Thread.Sleep(500);
         while (sw.ElapsedMilliseconds <= 5000);
        sw.Stop();
    

运行结果:

从上例中可以看出,我们使用了 Thread 类对象的 IsBackground 属性将 th 线程对象设置为了后台线程。不像前面的示例一样 th 对象可以顺利执行不超过五秒的内容,在上例中可以看到 th 对象只执行了 0.51 秒(并不固定)。这是因为停止所有前台线程后,运行时将停止所有后台线程并关闭。所以在主线程(前台线程)输出"主线程 (1) 等待…"之后就表示主线程停止了,后台线程也会停止并关闭,并不会执行不超过五秒的内容。

5、Thread 类的属性和方法

以一个表格展示 Thread 类常用的属性和方法。

Thread 类常用的属性:

属性名描述
ApartmentState获取或设置此线程的单元状态。
CurrentCulture获取或设置当前线程的区域性。
CurrentPrincipal获取或设置线程的当前负责人(对基于角色的安全性而言)。
CurrentThread获取当前正在运行的线程。
CurrentUICulture获取或设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源。
ExecutionContext获取 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
IsAlive获取指示当前线程的执行状态的值。
IsBackground获取或设置一个值,该值指示某个线程是否为后台线程。
IsThreadPoolThread获取指示线程是否属于托管线程池的值。
ManagedThreadId获取当前托管线程的唯一标识符。
Name获取或设置线程的名称。
Priority获取或设置指示线程的调度优先级的值。
ThreadState获取一个值,该值包含当前线程的状态。

Thread 类常用的方法:

方法名描述
Abort()在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。 调用此方法通常会终止线程。
Equals(Object)确定指定对象是否等于当前对象。(继承自 Object)
Finalize()确保垃圾回收器回收 Thread 对象时释放资源并执行其他清理操作。
GetApartmentState()返回表示单元状态的 ApartmentState 值。
GetCompressedStack()返回 CompressedStack 对象,此对象可用于获取当前线程的堆栈。
GetCurrentProcessorId()获取用于指示当前线程正在哪个处理器上执行的 ID。
GetDomain()返回当前线程正在其中运行的当前域。
GetDomainID()返回唯一的应用程序域标识符。
GetHashCode()返回当前线程的哈希代码。
GetType()获取当前实例的 Type。(继承自 Object)
Interrupt()中断处于 WaitSleepJoin 线程状态的线程。
Join()在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到由该实例表示的线程终止。
MemberwiseClone()创建当前 Object 的浅表副本。(继承自 Object)
ResetAbort()取消当前线程所请求的 Abort(Object)。
Resume()继续已挂起的线程。
SetApartmentState(ApartmentState)在线程启动前设置其单元状态。
SetCompressedStack(CompressedStack)将捕获的 CompressedStack 应用到当前线程。
Sleep(Int32)将当前线程挂起指定的毫秒数。
SpinWait(Int32)导致线程等待由 iterations 参数定义的时间量。
Start()导致操作系统将当前实例的状态更改为 Running。
ToString()返回表示当前对象的字符串。(继承自 Object)
TrySetApartmentState(ApartmentState)在线程启动前设置其单元状态。
UnsafeStart()导致操作系统将当前实例的状态更改为 Running。
VolatileRead(Byte)向字段中读取值。
VolatileWrite(Byte, Byte)向字段中写入值。

点击了解更多多线程的使用。


结语

🐆 以上就是 C# 多线程的介绍啦,希望对大家有所帮助。感谢大家的支持。


  1. 进程: 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。 ↩︎

  2. 并发: 指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。 ↩︎

  3. 主线程: 进程中第一个被执行的线程称为主线程。 ↩︎

  4. 子线程: 除了主线程外,在 C# 中使用 Thread t = new Thread(); 方式创建的线程均为子线程。 ↩︎

  5. 线程池: 是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。 ↩︎

以上是关于C#进阶C# 匿名方法的主要内容,如果未能解决你的问题,请参考以下文章

C#语言进阶——5.C# 的事件

面试题|干货!.NET C# 简答题Part 04

C# 2.0 中的新增功能03 匿名方法

C#委托(Delegate)简介

C#语言进阶——3.C# 的索引器

C#基础篇之语言和框架介绍