当前位置: 首页 > news >正文

C#基础--委托、lambda表达式和事件

文章目录

  • 委托
    • 声明委托
    • 使用委托
    • 委托示例
    • Action<T> 和 Func<T>
    • 多播委托
    • 匿名方法
  • lambda表达式
    • 参数
    • 多行代码
    • 闭包
  • 事件
    • 事件发布程序
    • 事件侦听器

委托

当要把方法传送给其他方法时,就需要使用委托:。

声明委托

声明委托的语法如下:

delegate void IntMethodlnvoker(int x);

在这个示例中,声明了一个委托IntMethodlnvoker,并指定该委托的每个实例都可以包含一个方法的引用,
该方法带有一个int参数,并返回voido理解委托的一个要点是它们的类型安全性非常高。在定义委托时,必
须给出它所表示的方法的签名和返回类型等全部细节。

使用委托

下面的代码段说明了如何使用委托。这是在int值上调用TbString()方法的一种相当冗长的方式

class Program
{
    private delegate string GetAString();

    static void Main()
    {
        int x = 40;
        GetAString firstStringMethod = new GetAString(x.ToString);
        Console.WriteLine($"String is {firstStringMethod()}");
    }
}

在这段代码中,实例化类型为GetAString的委托,并对它进行初始化,使其引用整型变量x的ToString()方法。在C#中,委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹
配最初定义委托时的签名。

委托示例

class MathOperations
{
    public static double MultiplyByTwo(double value) => value * 2;

    public static double Square(double value) => value * value;
}
delegate double DoubleOp(double x);

class Program
{
    static void Main()
    {
        DoubleOp[] operations =
        {
            MathOperations.MultiplyByTwo,
            MathOperations.Square
        };

        for (int i = 0; i < operations.Length; i++)
        {
            Console.WriteLine($"Using operations[{i}]:");
            ProcessAndDisplayNumber(operations[i], 2.0);
            ProcessAndDisplayNumber(operations[i], 7.94);
            ProcessAndDisplayNumber(operations[i], 1.414);
            Console.WriteLine();
        }
    }

    static void ProcessAndDisplayNumber(DoubleOp action, double value) =>
        Console.WriteLine($"Value is {value}, result of operation is {action(value)}");
}

在这段代码中,实例化了一个DoubleOp委托的数组。该数组的每个元素都初始化为 指向由MathOperations类实现的不同操作。然后遍历这个数组,把每个操作应用到3个不同的值上。这说明了 使用委托的一种方式一把方法组合到一个数组中来使用,这样就可以在循环中调用不同的方法了。
这段代码的关键一行是把每个委托实际传递给ProcessAndDisplayNumber方法,例如:

ProcessAndDisplayNumber(operations[i],   2.0);

其中传递了委托名,但不带任何参数。假定operations[i]是一个委托,在语法上:
• operations[i]表示“这个委托”。换言之,就是委托表示的方法。
• operationsi表示“实际上调用这个方法,参数放在圆括号中”。
ProcessAndDisplayNumber方法定义为把一个委托作为其第一个参数:

static void ProcessAndDisplayNumber(DoubleOp action, double value)

然后,在这个方法中,调用:

double result = action(value);

这实际上是调用action委托实例封装的方法,其返回结果存储在result中。运行这个示例,得到如下所示 的结果:
SimpleDelegate
Using operations[0]:
Value is 2, result of operation is 4
Value is 7.94, result of operation is 15.88
Value is 1.414, result of operation is 2.828
Using operations[1]:
Value is 2, result of operation is 4
Value is 7.94, result of operation is 63.0436
Value is 1.414, result of operation is 1.999396

Action 和 Func

Action委托表示引用一个void返回类型的方法。这个委托类存在不同的变体,可以传递至多16种不同的参 数类型。没有泛型参数的Action类可调用没有参数的方法。Actioni3用带一个参数的方法,Action<inTl, in T2>调用带两个参数的方法,Action<in Tl, in T2, in T3, in T4, in T5, in T6, in T7, in T8>调用带8个参数的方法。
Func委托可以以类似的方式使用。Func允许调用带返回类型的方法。与Action类似,Func 也定义了不同的变体,至多也可以传递16个参数类型和一个返回类型。Func委托类型可以调用带 返回类型且无参数的方法,Func<in T, out TResult>调用带一个参数的方法,Func<in Tl, in T2, in T3, in T4, out TResult>调用带4个参数的方法。
示例:

class Employee
{
    public Employee(string name, decimal salary)
    {
        Name = name;
        Salary = salary;
    }

    public string Name { get; }
    public decimal Salary { get; }

    public override string ToString() => $"{Name}, {Salary:C}";

    public static bool CompareSalary(Employee e1, Employee e2) =>
      e1.Salary < e2.Salary;
}

class BubbleSorter
{
    static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
    {
        bool swapped = true;
        do
        {
            swapped = false;
            for (int i = 0; i < sortArray.Count - 1; i++)
            {
                if (comparison(sortArray[i + 1], sortArray[i]))
                {
                    T temp = sortArray[i];
                    sortArray[i] = sortArray[i + 1];
                    sortArray[i + 1] = temp;
                    swapped = true;
                }
            }
        } while (swapped);
    }
}

 class Program
 {
     static void Main()
     {
         Employee[] employees =
         {
             new Employee("Bugs Bunny", 20000),
             new Employee("Elmer Fudd", 10000),
             new Employee("Daffy Duck", 25000),
             new Employee("Wile Coyote", 1000000.38m),
             new Employee("Foghorn Leghorn", 23000),
             new Employee("RoadRunner", 50000)
         };

         BubbleSorter.Sort(employees, Employee.CompareSalary);

         foreach (var employee in employees)
         {
             Console.WriteLine(employee);
         }
     }
 }

多播委托

如果要调用多个方 法,就需要多次显式调用这个委托。但是,委托也可以包含多个方法。这种委托称为多播委托。如果调用多播
委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void;否则,就只能得到委托调用的最 后一个方法的结果。

class MathOperations
{
    public static void MultiplyByTwo(double value) =>
        Console.WriteLine($"Multiplying by 2: {value} gives {value * 2}");

    public static void Square(double value) =>
        Console.WriteLine($"Squaring: {value} gives {value * value}");
}

class Program
 {
     static void Main()
     {
         Action<double> operations = MathOperations.MultiplyByTwo;
         operations += MathOperations.Square;

         ProcessAndDisplayNumber(operations, 2.0);
         ProcessAndDisplayNumber(operations, 7.94);
         ProcessAndDisplayNumber(operations, 1.414);
         Console.WriteLine();
     }

     static void ProcessAndDisplayNumber(Action<double> action, double value)
     {
         Console.WriteLine();
         Console.WriteLine($"ProcessAndDisplayNumber called with value = {value}");
         action(value);
     }
 }

匿名方法

匿名方法是用作委托的参数的一段代码。用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就会出现区别。

class Program
{
    static void Main()
    {
        string mid = ", middle part,";

        Func<string, string> anonDel = delegate (string param)
        {
            param += mid;
            param += " and this was added to the string.";
            return param;
        };
        Console.WriteLine(anonDel("Start of string"));
    }
}

可以看出,该代码块使用方法级的字符串变量mid,该变量是在匿名方法的外部定义的,并将其添加到要传递的参数中。接着代码返回该字符串值。在调用委托时,把一个字符串作为参数传递,将返回的字符串输出 到控制台上。

lambda表达式

使用lambda表达式的一个场合是把lambda表达式赋予委托类型:在线实现代码。只要有委托参数类型的地 方,就可以使用lambda表达式。

class Program
{
	static void Main()
	{
		string mid = ",middle part,";
		Func<string, string> lambda = param =>
		{
			param += mid;
			param += " and this was added to the string. "; 
			return param;
		};
		Console.WriteLine(lambda("Start of string"));
	}
}

参数

lambda表达式有几种定义参数的方式。如果只有一个参数,只写出参数名就足够了。下面的lambda表达式 使用了参数So因为委托类型定义了一个string参数,所以s的类型就是string。实现代码调用String.FormatO方法 来返回一个字符串,在调用该委托时,就把该字符串最终写入控制台:

Func<string, string> oneParam = s => $"change uppercase {s .ToUpper () } **; 
Console.WriteLine(oneParam("test"));

如果委托使用多个参数,就把这些参数名放在圆括号中。这里参数x和y的类型是double,由Func<double,
double, double>委托定义:

Func<double, double, double> twoParams = (x, y)=> x * y; 
Console.WriteLine(twoParams(3, 2));

为了方便起见,可以在圆括号中给变量名添加参数类型。如果编译器不能匹配重载后的版本,那么使用参
数类型可以帮助找到匹配的委托:

Func<double, double, double> twoParamsWithTypes = 
(double x, double y) => x * y;
Console.WriteLine(twoParamsWithTypes(4,  2));

多行代码

如果lambda表达式只有一条语句,在方法块内就不需要花括号和return语句,因为编译器会添加一条隐式的return 语句:

Func<double, double> square = x => x * x;

添加花括号、return语句和分号是完全合法的,通常这比不添加这些符号更容易阅读:

Func<double,double> square = x =>return x * x;

但是,如果在lambda表达式的实现代码中需要多条语句,就必须添加花括号和return语句:

Func<string, string> lambda = param =>
{
	param += mid;
	param += " and this was added to the string. "; 
	return param;
};

闭包

通过lambda表达式可以访问lambda表达式块外部的变量,这称为闭包。

int someVal = 5;
Func<int, int> f = x => x + someVal;
someVal = 7;
Console.WriteLine(f(3));
Console.WriteLine();

如果给多个线程使用闭包,就可能遇到并发冲突。最好仅给闭包使用不变的类型。这样可以确保不改变值, 也不需要同步。

事件

事件基于委托,为委托提供了一种发布/订阅机制。在.NET架构内到处都能看到事件。在Windows应用
程序中,Button类提供了 Click事件。这类事件就是委托。触发CUck事件时调用的处理程序方法需要得到定义, 而其参数由委托类型定义。

事件发布程序

我们从CarDealer类开始介绍,它基于事件提供一个订阅。在NewCarQ方法中,通过调用 RaiseNewCarInfo 方法触发,这个方法的实现确认委托是否为空,如果不为空,就引发事件。

public class CarInfoEventArgs : EventArgs
{
     public CarInfoEventArgs(string car) => Car = car;

     public string Car { get; }
 }

 public class CarDealer
 {
     public event EventHandler<CarInfoEventArgs> NewCarInfo;

     public void NewCar(string car)
     {
         Console.WriteLine($"CarDealer, new car {car}");

         NewCarInfo?.Invoke(this, new CarInfoEventArgs(car));
     }
 }

CarDealer 类提供了 EventHandler<CarInfoEventAi^s>类型的NewCarlnfo 事件。作为一个约定,事件一般使 用带两个参数的方法;其中第一个参数是一个对象,包含事件的发送者,第二个参数提供了事件的相关信息。 第二个参数随不同的事件类型而改变。
CarDealer类通过调用委托的Invoke方法触发事件。可以调用给事件订阅的所有处理程序。注意,与之前 的多播委托一样,方法的调用顺序无法保证。为了更多地控制处理程序的调用,可以使用Delegate类的 GetInvocationList()方法,访问委托列表中的每一项,并独立地调用每个方法,如上所示。

事件侦听器

Consumer类用作事件侦听器。这个类订阅了 CarDealer类的事件,并定义了 NewCarlsHere方法,该方法满足EventHandler<CarInfoEventArgs〉委托的要求,该委托的参数类型是object和CarlnfoEventArgs。

public class Consumer
{
    private string _name;

    public Consumer(string name) => _name = name;

    public void NewCarIsHere(object sender, CarInfoEventArgs e) =>
      Console.WriteLine($"{_name}: car {e.Car} is new");
}

现在需要连接事件发布程序和订阅器。为此使用CarDealer类的NewCarlnfo事件,通过“+=”创建一个订阅。消费者Valtteri订阅了事件,接着消费者Max也订阅了事件,然后Valtteri通过取消了订阅。

class Program
{
    static void Main()
    {
        var dealer = new CarDealer();
        var valtteri = new Consumer("Valtteri");
        dealer.NewCarInfo += valtteri.NewCarIsHere;
        dealer.NewCar("Williams");
        var max = new Consumer("Max");
        dealer.NewCarInfo += max.NewCarIsHere;
        dealer.NewCar("Mercedes");
        dealer.NewCarInfo -= valtteri.NewCarIsHere;
        dealer.NewCar("Ferrari");
    }
}

运行应用程序,一辆Williams汽车到达,Valtteri得到了通知。因为之后Max也注册了该订阅,所以Valtteri 和Max都获得了新款Mercedes汽车的通知。接着Valtteri取消了订阅,所以只有Max获得了 Ferrari汽车的 通知:
CarDealer, new car Williams
Valtteri: car Williams is new
CarDealer, new car Mercedes
Valtteri: car Mercedes is new
Max: car Mercedes is new
CarDealer, new car Ferrari
Max: car Ferrari is new

相关文章:

  • LeetCode 0316. 去除重复字母:单调栈
  • 算法-二叉树
  • 基于统计自适应线性回归的目标尺寸预测
  • springboot-鑫源停车场管理系统 毕业设计 -附源码 290915
  • java题
  • 记录一次超大(200+G)数据量导入ES的解决办法
  • MySQL进阶第五天——存储过程与存储函数
  • 无重复字符的最长子串(力扣中等难度)
  • 热加载原理解析与实现
  • re正则表达式
  • vector模拟实现
  • 【网络安全】记一次简单渗透测试实战
  • 1200万像素通过算法无失真扩展到1.92亿像素——加权概率模型收缩模型图像像素扩展算法
  • Java高级学习篇之反射
  • Java中利用new和反射得到对象的区别(底层)
  • SegmentFault for Android 3.0 发布
  • css系列之关于字体的事
  • egg(89)--egg之redis的发布和订阅
  • Flex布局到底解决了什么问题
  • gitlab-ci配置详解(一)
  • java中具有继承关系的类及其对象初始化顺序
  • k8s如何管理Pod
  • leetcode388. Longest Absolute File Path
  • Lsb图片隐写
  • oschina
  • QQ浏览器x5内核的兼容性问题
  • scrapy学习之路4(itemloder的使用)
  • VUE es6技巧写法(持续更新中~~~)
  • 对超线程几个不同角度的解释
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 使用 @font-face
  • 一起参Ember.js讨论、问答社区。
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • 从如何停掉 Promise 链说起
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • (3)选择元素——(17)练习(Exercises)
  • (C++20) consteval立即函数
  • (webRTC、RecordRTC):navigator.mediaDevices undefined
  • (二)斐波那契Fabonacci函数
  • (二)换源+apt-get基础配置+搜狗拼音
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (利用IDEA+Maven)定制属于自己的jar包
  • (一)Thymeleaf用法——Thymeleaf简介
  • (转)EXC_BREAKPOINT僵尸错误
  • .mysql secret在哪_MYSQL基本操作(上)
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET CORE 第一节 创建基本的 asp.net core
  • .NET 中使用 Mutex 进行跨越进程边界的同步
  • .NET使用存储过程实现对数据库的增删改查
  • @AliasFor注解
  • @DateTimeFormat 和 @JsonFormat 注解详解
  • @SuppressWarnings(unchecked)代码的作用