C# 委托

107 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情 >>

前言

今天带来的是委托相关技术博客,此文是之前学习C#委托的时候的笔记,今天正好复习一下。

委托是什么

委托是定义方法签名的引用类型数据类型。 您可以定义委托的变量,就像其他数据类型一样,可以引用与委托具有相同签名的任何方法。

我们使用委托时会涉及到三个步骤:

  • 声明一个委托 Delegate
  • 设置目标方法
  • 调用委托 Delegate

委托定义

  1. 声明委托:

[访问修饰符] delegate [返回类型] [委托名称]([参数...])

例如publicdelegatevoidMyDelegate(string msg);

  1. 声明委托后,我们需要设置目标方法或 lambda 表达式。 我们可以通过使用 new 关键字创建委托对象并传递签名与委托签名匹配的方法来实现。再进行调用委托
using System;

namespace ConsoleApp1
{
    public delegate void MyDelegate(string msg); // 声明一个委托
    class Program
    {
        // 跟MyDelegate有相同参数和返回值的方法
        static void MethodA(string message)
        {
            Console.WriteLine(message);
        }

        static void Main(string[] args)
        {
            // 把函数传给委托
            MyDelegate del1 = new MyDelegate(MethodA);
            // 直接赋值给委托
            MyDelegate del2 = MethodA;
            // 使用 lambda表达式
            MyDelegate del3 = (string msg) => Console.WriteLine(msg);

            del1.Invoke("1");
            del2("2");
            del3("3");

            Console.ReadKey();
        }
    }
}
  • 委托也可以当作一个参数进行传递

ex:

using System;

namespace ConsoleApp1
{
    public delegate void MyDelegate(string msg); // 声明一个委托
    class Program
    {
        static void Main(string[] args)
        {
            MyDelegate del = ClassA.MethodA;
            InvokeDelegate(del);

            del = ClassB.MethodB;
            InvokeDelegate(del);

            del = (string msg) => Console.WriteLine("调用 lambda 表达式: " + msg);
            InvokeDelegate(del);
        }

        static void InvokeDelegate(MyDelegate del) // 参数是一个委托
        {
            del("Hello World");
        }
    }

    public class ClassA
    {
        public static void MethodA(string message)
        {
            Console.WriteLine("调用 ClassA.MethodA(): " + message);
        }
    }

    public class ClassB
    {
        public static void MethodB(string message)
        {
            Console.WriteLine("调用 ClassB.MethodB(): " + message);
        }
    }
}

多播委托

  • 一个委托实例可以引用一组目标方法,所有的委托实例都具有多播的功能。
  • 使用+或+=操作符就可以合并委托实例,合并后调用就会顺序地调用各个委托实例委托的方法,调用的顺序跟合并的顺序一致。
  • 使用-或-=操作访可以移除合并的委托实例。
  • C#会把这些操作符编译成System.Delegate中的Combine和Remove两个静态方法
class Program
{
    public delegate int Transformer(int x);
    static void Main(string[] args)
    {
        Transformer t = null;
        //在这里方法也可以当成是一个委托实例
        //Transformer t = Square
        t += Square;
        //t = t + Cube
        t += Cube;

        //调用的时候就会按照Square、Cube顺序进行调用
        //返回最后一个方法的返回值
        var i = t(3);
        Console.WriteLine(i);

        //移除委托实例
        //若把所有的委托实例都移除了,那么委托实例会为null
        t -= Cube;

        var j = t(3);
        Console.WriteLine(j);

    }

    static int Square(int x)
    {
        var result = x * x;
        Console.WriteLine(result);
        return result;
    }

    static int Cube(int x)
    {
        var result = x * x * x;
        Console.WriteLine(result);
        return result;
    }
}

泛型委托

  • 委托类型可以包含泛型类型的参数,public delegate T Transformer (T arg);

Func和Action委托

Func

  • Func类型的定义都放在了System命名空间下,它代表的是有返回值的委托类型,它有多种不同的泛型类型可供使用:
    1. delegate TResult Func();:有返回值,没有参数的委托类型,out表示TResult类型的变量只能作为方法的输出。
    2. delegate TResult Func<in T, out TResult>(T arg);:有返回值,一个参数的委托类型,in表示T类型的变量只能作为方法的输入。
    3. delegate TResult Func<in T1, in T2... out TResult>(T1 arg1, T2 arg2...);:有返回值,多个个参数的委托类型,最多可以支持16个输入参数。

Action

  • Action类型的定义都放在了System命名空间下,它代表的是没有返回值的委托类型,它有多种不同的泛型类型可供使用:
  1. delegate void Action();:没有返回值,没有参数的委托类型。
  2. delegate void Action(T arg);:没有返回值,一个参数的委托类型。
  3. delegate void Action<in T1, in T2...>(T1 arg1, T2 arg2...);:没有返回值,多个个参数的委托类型,最多可以支持16个输入参数。
class Program
{
    static void Main(string[] args)
    {
        int[] values1 = {1, 2, 3};

        //使用Func委托类型进行委托
        Transform1(values1, Square);
        foreach (var value in values1)
        {
            Console.WriteLine(value);
        }

        //使用Action委托类型进行委托
        int[] values2 = { 1, 2, 3 };
        Transform2(values2, Cube);
    }

    static void Transform1<T>(T[] values, Func<T, T> tranFunc)
    {
        for (int i = 0; i < values.Length; i++)
        {
            //Func有返回值
            values[i] = tranFunc(values[i]);
        }
    }

    static void Transform2<T>(T[] values, Action<T> tranAction)
    {
        for (int i = 0; i < values.Length; i++)
        {
            //Action没有返回值
            tranAction(values[i]);
        }
    }

    static int Square(int x) => x * x;

    static void Cube(int x) => Console.WriteLine(x*x*x);
}

\

断言委托 Predicate

Predicate 是类似于 FuncAction 委托的委托。 它表示一个包含一组标准的方法,并检查传递的参数是否满足这些标准。 Predicate委托方法必须接受一个输入参数并返回一个布尔值 - 真或假。

Predicate 委托在 System 命名空间中定义,如下所示:

public delegate bool Predicate<in T>(T obj);

与其他委托类型一样,Predicate 也可以与任何方法、匿名方法或 lambda 表达式一起使用。

class Program
{
    static bool IsUpperCase(string str)
    {
        return str.Equals(str.ToUpper());
    }

    static void Main(string[] args)
    {
        Predicate<string> isUpper = IsUpperCase;

        bool result = isUpper("hello world!!");

        Console.WriteLine(result); //False
    }
}

接口和委托

  • 委托可以解决的委托,接口都可以解决,那在什么时候更适合自定义的委托而不是使用接口呢?
    1. 需要多播的功能,接口不能实现多播的功能
    2. 订阅者需要多次实现接口

委托的兼容性

  • 委托类型:委托类型之间时互不相容的,即使方法签名一样。
  • 委托实例:如果委托实例拥有相同的方法目标,那么委托实例就是相等的。
  • 参数:委托可以接受比它的定义参数类型更加抽象的参数类型的方法,这个叫做委托的逆变。
  • 返回值:委托可以接受比它的定义返回类型更加具体的返回类型的方法,这个叫做委托的协变。

lambda表达式

示例lambda

ex:使用匿名方法检查学生是否是青少年

public class Student
{
    public int Id { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }
}
static void Main(string[] args)
{
    Predicate<Student> predicate = delegate (Student s) { return s.Age > 12 && s.Age < 20; };
}

上述匿名方法可以使用 C# Lambda 表达式表示如下:

class Program
{
    static void Main(string[] args)
    {
        Predicate<Student> predicate = s => s.Age > 12 && s.Age < 20; 
    }
}

lambda的演化过程

让我们看看 lambda 表达式是如何从以下匿名方法演变而来的

匿名方法

delegate (Student s) { return s.Age > 12 && s.Age < 20; };

Lambda表达式

static void Main(string[] args)
{
    Predicate<Student> predicate = delegate (Student s) { return s.Age > 12 && s.Age < 20; };
    // 把 delegate 去掉了 加入了 => 关键的一步。换了一种表达方式而已
    Predicate<Student> predicate2 = (Student s) => { return s.Age > 12 && s.Age < 20; };
    // 把类型去掉了。因为是可以推断出来的。
    Predicate<Student> predicate3 = (s) => { return s.Age > 12 && s.Age < 20; };

    //因为只有一个语句,所以 return 和大括号也可以被省略
    Predicate<Student> predicate4 = (s) =>  s.Age > 12 && s.Age < 20;

    // 因为只有一个参数所以 括号也可以补省略
    Predicate<Student> predicate5 = s => s.Age > 12 && s.Age < 20;
}

lambda多个参数

//类型参数可以省是因为左边有定义了,所以推断得出来
Func<Student, int, bool> func = (s, minAge) => { return s.Age > minAge && s.Age < 20; };

lambda 无参

Action action = () => Console.WriteLine("hello world");

lambda表达式多个语句

Predicate<Student> predicate6 = (s) =>
{
    s.Age += 10;
    return s.Age > 12 && s.Age < 20;
};

lambda 表达式里定义变量

Predicate<Student> predicate7 = (s) =>
{
    var maxAge = 20;
    return s.Age > 12 && s.Age < maxAge;
};

\

lambda 表达式使用外部的变量

 var maxAge = 20;
Predicate<Student> predicate8 = (s) =>
{
    return s.Age > 12 && s.Age < maxAge;
};

事件