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


    委托

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

    声明委托

    声明委托的语法如下:

    delegate void IntMethodlnvoker(int x);
    
    • 1

    在这个示例中,声明了一个委托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()}");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这段代码中,实例化类型为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)}");
    }
    
    • 1
    • 2
    • 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

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

    ProcessAndDisplayNumber(operations[i],   2.0);
    
    • 1

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

    static void ProcessAndDisplayNumber(DoubleOp action, double value)
    
    • 1

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

    double result = action(value);
    
    • 1

    这实际上是调用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调用带两个参数的方法,Action调用带8个参数的方法。
    Func委托可以以类似的方式使用。Func允许调用带返回类型的方法。与Action类似,Func 也定义了不同的变体,至多也可以传递16个参数类型和一个返回类型。Func委托类型可以调用带 返回类型且无参数的方法,Func调用带一个参数的方法,Func调用带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);
             }
         }
     }
    
    • 1
    • 2
    • 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
    • 61

    多播委托

    如果要调用多个方 法,就需要多次显式调用这个委托。但是,委托也可以包含多个方法。这种委托称为多播委托。如果调用多播
    委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回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);
         }
     }
    
    • 1
    • 2
    • 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

    匿名方法

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

    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"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可以看出,该代码块使用方法级的字符串变量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"));
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    参数

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

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

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

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

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

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

    多行代码

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

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

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

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

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

    Func<string, string> lambda = param =>
    {
    	param += mid;
    	param += " and this was added to the string. "; 
    	return param;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    闭包

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

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

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

    事件

    事件基于委托,为委托提供了一种发布/订阅机制。在.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));
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    事件侦听器

    Consumer类用作事件侦听器。这个类订阅了 CarDealer类的事件,并定义了 NewCarlsHere方法,该方法满足EventHandler

    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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    现在需要连接事件发布程序和订阅器。为此使用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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行应用程序,一辆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

  • 相关阅读:
    《软件方法》2023版第1章(09)基本共识上的沟通,SysML
    Java --- Spring6前的程序问题
    PHP基于thinkphp的旅游见闻管理系统#毕业设计
    Linux的一些知识(7)
    泰山OFFICE技术讲座:着重号的大小与字号关系
    如何使用ArcGIS Pro制作个性三维地形图
    tomcat部署
    第 18章 安全架构设计理论与实践
    Mysql 索引与事务
    XML配置文件(DTD详细讲解)
  • 原文地址:https://blog.csdn.net/huan13479195089/article/details/126986329