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

文章目录

  • 委托
    • 声明委托
    • 使用委托
    • 委托示例
    • Action 和 Func
    • 多播委托
    • 匿名方法
  • 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调用带两个参数的方法,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);
         }
     }
 }

多播委托

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

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类型的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");
}

现在需要连接事件发布程序和订阅器。为此使用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

你可能感兴趣的:(C#,基础,c#,委托,事件,lambda,Function)