C#委托的学习笔记

委托

感谢微软MVP杨旭老师的精彩讲解
真会C#? - 第4章 委托、事件、Lambda表达式(完结)_哔哩哔哩_bilibili

之所以你没有使用委托是因为你还不够了解它.._哔哩哔哩_bilibili

Delegates委托

委托类型定义了委托实例可以调用的某类方法(规定了调用的方法的返回类型参数

委托类型可以在命名空间声明

基本声明与调用

***注意“委托类型”和“委托实例”***

public delegate void Help();//定义一个返回值为空,委托类型为Help的 无参委托类型
public delegate int Transformer(int x);//定义一个返回值为int,委托类型为Transformer,参数为int型的带参委委托类型

public static int Square(int i) => i * i;//声明一个方法Square

Help h;//声明一个委托实例h
Transformer t = Square;//创建了委托实例t(委托对象t)

int answer = t(3);//调用 answer=9 完整调用写法:int answer = t.Invoke(3);

编写插件式的写法

class Util
{
    //注意方法参数
    public static void Transform(int[] values, Transformer t)
    {
        for (int i = 0; i < values.Length; i++)
        {
            values[i] = t(values[i]);
        }
    }
}

static int Square(int x) => x * x;
static int AddOne(int x) => x + 1;

int[] values = { 1, 2, 3 };
Util.Transform(values, Square);//将Square方法作为参数传入Transform方法

foreach (int item in values)
{
    Console.WriteLine(item.ToString());//循环输出1,4,9
}

Util.Transform(values, AddOne);
foreach (int item in values)
{
    Console.WriteLine(item.ToString());//循环输出2,5,10
}

多播委托

所有的委托实例都具有多播的能力

一个委托实例可以引用一组目标方法

可以使用+和+=操作合并委托实例

委托的调用顺序和它们的定义顺序一致(与合并顺序一致)

    public delegate int Transformer(int x);
    class Program
    {
        static int Square(int x)
        {
            var result = x * x;
            Console.Write($"执行平方后:{result} ");
            return result;
        }
        static int Cube(int x)
        {
            var result = x * x * x;
            Console.Write($"执行立方后:{result} ");
            return result;
        }
        static void Main(string[] args)
        {
            Transformer t = null;
            t += Square;
            t += Cube;
            var result = t(3);
            Console.WriteLine("结果:" + result);
            //输出"执行平方后:9 执行立方后:27 结果:27"

            //如果多播委托的返回类型不是void,那么被调用者从最后一个被调用的的方法来接收返回值。
            //前面的方法仍被调用,但结果被弃用
            t -= Square;//从t中移除Square方法
            Console.WriteLine("结果:" + result);//输出 "结果:27"
        }
    }

委托是不可变的,使用+=、-=操作符时,实际上是创建了新的委托实例,并将它赋值给当前的委托实例

多播委托(MulticastDelegate)继承自 Delegate ,表示多路广播委托;即,其调用列表中可以拥有多个元素的委托。实际上,我们自定义的委托的基类就是 MulticastDelegate。

C#会把作用于委托的+,-,+=,-=操作自动编译成System.Delegate的Combin和Remove两个静态方法

实例方法目标和静态方法目标

  • 委托实例合并的一组方法,有的方法是实例方法(需要实例化对象后通过对象调用的),有的方法为静态方法 (static)
  • 当一个实例方法被赋值给委托对象的时候,这个委托对象不仅保留着对方法的引用,还要保留着方法所属实例的引用
  • System.Delegate的Target属性就代表着这个被调用的对象
  • 如果引用的是静态方法,那么Target属性的值就是null
    public delegate void ProgressReport(int percentComplete);
    class X
    {
        public void InstanceProgress(int percentComplete) => Console.WriteLine(percentComplete);
    }
    class Program
    {
        static void Main(string[] args)
        {
            X x = new X();
            ProgressReport p = x.InstanceProgress;
            p(99);//99
            Console.WriteLine(p.Target == x);//True
            //输出委托实例的方法
            Console.WriteLine(p.Method);//Void InstanceProgress(Int32)
        }

泛型委托类型

委托类型可以包含泛型类型参数

    public delegate T Transformer<T>(T arg);
    class Util
    {
        public static void Transform<T>(T[] values, Transformer<T> t)
        {
            for (int i = 0; i < values.Length; i++)
            {
                values[i] = t(values[i]);
            }
        }
    }
    class Program
    {
        static int Square(int x) => x * x;
        static void Main(string[] args)
        {
            int[] values = { 1, 2, 3 };
            Util.Transform(values, Square);
            foreach (var item in values)
            {
                Console.Write(item + " ");//1,4,9
            }
        }
    }

Func和Action委托

使用泛型委托,就可以写出一组委托类型,它们可以调用的方法可以拥有任意的返回类型和任意数量的参数

Func无返回值

Action有返回值

与上则代码相比屏蔽了委托类型的定义,并将作为参数的泛型委托替换为Func,运行结果一致

//public delegate T Transformer<T>(T arg);
    class Util
    {
        public static void Transform<T>(T[] values, Func<T, T> t)
        {
            for (int i = 0; i < values.Length; i++)
            {
                values[i] = t(values[i]);
            }
        }
    }
    class Program
    {
        static int Square(int x) => x * x;
        static string WR(string x) => "WR:" + x;
        static void Main(string[] args)
        {
            int[] values = { 1, 2, 3 };
            string[] strArr = { "逆变", "协变" };

            Util.Transform(values, Square);
            foreach (var item in values)
            {
                Console.Write(item + " ");//1,4,9
            }

            Util.Transform(strArr, WR);
            foreach (var item in strArr)
            {
                Console.Write(item + " ");//WR:逆变 WR:协变
            }
        }
    }

委托与接口

委托可以解决的问题,接口都可以解决

什么情况下选择委托而非接口:

  • 接口只能定义一个方法
  • 需要多播能力
  • 订阅者需要多次实现接口

委托的兼容性

委托类型

委托类型之间互不兼容,即使委托方法签名一样

        public delegate int D1();
        public delegate int D2();
        D1 d1 = ()=>1;
        D2 d2 = d1;//此句报错

委托实例

委托实例如果拥有相同的方法目标,那么委托实例就认为是相等

static void test()
{
    Console.WriteLine("");
}

D1 d1 = () => Console.WriteLine("");

D1 d2 = test;
D1 d3 = test;

Console.WriteLine(d1 == d2);//False
Console.WriteLine(d2 == d3);//True

参数

当你调用一个方法时,你提供的参数可以比方法定义的参数更加具体

委托的协变逆变仅支持引用转换支持值类型转换,与泛型一致

    delegate void stringAction(string s);
    class Program
    {
        static void ActOnObj(object obj) => Console.WriteLine(obj);
        static void Main(string[] args)
        {
            stringAction sa = new stringAction(ActOnObj);//带参实例化委托实例
            sa("Hello World");
        }
    }

返回类型

调用方法得到的返回值类型可以比方法定义的返回值类型更加具体

    delegate object ObjectRetriever();
    class Test
    {
        static void Main(string[] args)
        {
            ObjectRetriever o = new ObjectRetriever(RetrieveString);
            object res = o();
            Console.WriteLine(res);//Hello
        }
        static string RetrieveString() => "Hello";
    }
posted @ 2021-12-07 13:05  ₩Qi  阅读(50)  评论(0编辑  收藏  举报