04.委托Delegation

Posted 位永光

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了04.委托Delegation相关的知识,希望对你有一定的参考价值。

1. 基本了解

1.1 委托简述

官方文档

委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用,用于将方法作为参数传递给其他方法,可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托

其它说明

委托在IL中就是一个类(本质上是类),继承与System.MulticastDelegate类(特殊类)

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递

委托类型

自定义委托:Delegate

系统内置委托:ActionFunc

委托声明

可以声明在类的外部也可以在内部,在IL中,无论在内外都会编译到类的内部

委托在实例化时,需要传入一个方法,此方法返回值,参数(类型,个数,顺序)与委托一致

1.2 使用步骤

  • 声明一个委托
  • 委托的实例化,传入指定方法
  • 调用执行

2. Delegate委托

Delegate:常用到的一种声明,且至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型

2.1 示例一:无参,无返回值

// 1.声明委托
public delegate void NoReturnNoPare();

// 2.准备委托执行方法
public void Show()
{
    Console.WriteLine("无参,无返回值");
}

// 3.实例,调用委托
public void Start()
{
    NoReturnNoPare d1 = new NoReturnNoPare(this.Show);
    d1.Invoke();
}

2.2 示例二:有参,无返回值

// 1.声明委托
public delegate void NoReturnWithPare(int x, int y);

// 2.准备委托执行方法
public void Show(int x,int y)
{
    Console.WriteLine("有参,无返回值");
}

// 3.实例,调用委托
public void Start()
{
    NoReturnWithPare d2 = new NoReturnWithPare(this.Show);
    d2.Invoke(1,2);
}

2.3 示例三:有参,有返回值

// 1.声明委托
public delegate int WithReturnWithPare(int x, int y);

// 2.准备委托执行方法
public int Show(int x, int y)
{
    return x + y;
}

// 3.实例,调用委托
public void Start()
{
    WithReturnWithPare d2 = new WithReturnWithPare(this.Show);
    // 返回值类型,编译器会自动推断
    int IResult = d2.Invoke(1, 2);
    Console.WriteLine(IResult);
}

3. Action委托

Action是系统内置委托(无需声明),是无返回值的泛型委托,至少0个参数,至多16个参数,且无返回值

3.1 示例一:无参,无返回值

// 1.定义执行方法
public void Show()
{
    Console.WriteLine("无参,无返回值");
}

// 2.调用执行
public void Start()
{
    Action action = new Action(this.Show);
    // Action<int, int> action = this.Show;
    action.Invoke();
}

3.2 示例二:有参,无返回值

// 1.定义执行方法
public void Show(int x, int y)
{
    Console.WriteLine("有参,无返回值");
}

// 2.调用执行
public void Start()
{
    Action<int, int> action = new Action<int,int>(this.Show);
    // Action<int, int> action = this.Show;
    action.Invoke(1,2);
}

3.3 示例三:使用 lambda 表达式

Action<int, int> action = (x, y) => { };
action.Invoke(1, 2);

3.4 示例四:将委托作为方法参数

public void Start()
{
    Action<int> action = (x) => { Console.WriteLine(x); };
    Show(action, 2);
}

public void Show<T>(Action<T> ac, T inputParam)
{
    ac(inputParam);
}

4. Func委托

Func是有返回值的泛型委托,至少0个参数,至多16个参数,根据返回值泛型返回;必须有返回值,不可void,且最后一位泛型类型,为返回值类型

4.1 示例一:无参,有返回值

public void Start()
{
    Func<string> func = new Func<string>(this.Show);
    string IResult = func.Invoke();
    Console.WriteLine(IResult);
}

public string Show()
{
    return "libai";
}

4.2 示例二:有参,有返回值

public void Start()
{
    Func<int, string> func = new Func<int, string>(this.Show);
    string IResult = func.Invoke(1);
    Console.WriteLine(IResult);
}

public string Show(int i)
{
    return "libai\\t" + i;
}

4.3 示例三:使用lambda表达式

public void Start()
{
    Func<int> func1 = () => { return 1; };
    int IResultInt = func1.Invoke();
    Console.WriteLine(IResultInt);

    Func<int, string> func2 = (i) => { return i.ToString(); };
    string IResult = func2.Invoke(1);
    Console.WriteLine(IResult);
}

4.4 示例四:将委托作为方法参数

例子一:简化

public void Start()
{
    Func<int, int, int> func = (x, y) => { return x + y; };
    int IResultInt = Test(func, 1, 2);
    Console.WriteLine(IResultInt);
}

public int Test<T1, T2>(Func<T1, T2, int> func, T1 a, T2 b)
{
    return func(a, b);
}

示例二:一般写法

static void Main(string[] args)
{
    Console.WriteLine(Test<int,int>(Fun,100,200));
    Console.ReadKey();
}
public static int Test<T1, T2>(Func<T1, T2, int> func, T1 a, T2 b)
{
    return func(a, b);
}
private static int Fun(int a, int b)
{
    return a + b;
}

5. 链式委托

5.1 文档说明

官方文档

委托对象的一个有用属性在于可通过使用 + 运算符将多个对象分配到一个委托实例,多播委托包含已分配委托列表,此多播委托被调用时会依次调用列表中的委托;仅可合并类型相同的委托

  • - 运算符可用于从多播委托中删除组件委托,顺序,从下至上(委托列表中没有移除的委托时不会报错)
  • + 运算符可用于将委托组件添加到委托列表,顺序,从上而下

在执行有返回值的委托链时,只能得到最后一个委托的结果

其它文档

委托链(多播委托)是一个由委托组成的链表,而不是一个新的东西,所有的自定义委托都直接集成自System.MulticastDelegate类型,这个类型即是为委托链而设计的

链式委托是指一个委托的链表,而不是指另外一类特殊的委托,当执行链上的一个方法时,后续委托将会被依此执行

System.MuticastDelegate定义了对链式委托的支持,在System.Delegate的基础上,增加了一个指向后续委托的指针,这样就实现了一个简单的链表结构

5.2 示例一:统一执行

public void Start()
{
    NoReturnWithPare noReturnWith = new NoReturnWithPare(this.Fun1);
    noReturnWith += this.Fun2;
    noReturnWith += this.Fun1;
    noReturnWith.Invoke(1, 2);
}

public void Fun1(int x, int y)
{
    Console.WriteLine("Fun1:\\t" + x + y);
}

public void Fun2(int x, int y)
{
    Console.WriteLine("Fun2:\\t" + x + y);
}

5.3 示例二:逐个执行

注意:逐个执行时,单项不能用var声明,必须使用委托的具体类型

逐个指定,返回值,问题,b委托使用a的返回值?

public void Start()
{
    NoReturnWithPare noReturnWith = new NoReturnWithPare(this.Fun1);
    noReturnWith += this.Fun2;
    noReturnWith += this.Fun1;
    foreach (NoReturnWithPare item in noReturnWith.GetInvocationList())
    {
        item.Invoke(1,2);
    }
}

public void Fun1(int x, int y)
{
    Console.WriteLine("Fun1:\\t" + x + y);
}

public void Fun2(int x, int y)
{
    Console.WriteLine("Fun2:\\t" + x + y);
}

5.4 示例三:责任链模式

典型案例:用代码模拟,猫叫了,狗叫了,然后老鼠跑了

普通实现

using System;

namespace de2
{
    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat();
            cat.Miao();
        }
    }

    public class Cat
    {
        public void Miao()
        {
            Console.WriteLine("猫叫了");
            new Dog().Wang();
            new Mouse().Run();
        }
    }
    public class Dog
    {
        public void Wang()
        {
            Console.WriteLine("狗叫了");
        }
    }
    public class Mouse
    {
        public void Run()
        {
            Console.WriteLine("老鼠跑了");
        }
    }
}

使用责任链模式实现(委托)

using System;

namespace de2
{
    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat();

            cat.miaoAction += new Dog().Wang;
            cat.miaoAction += new Mouse().Run;

            cat.MiaoDelegate();
        }
    }

    public class Cat
    {
        public void Miao()
        {
            Console.WriteLine("猫叫了");
        }

        public Action miaoAction;
        public void MiaoDelegate()
        {
            this.Miao();
            this.miaoAction.Invoke();
        }
    }
    public class Dog
    {
        public void Wang()
        {
            Console.WriteLine("狗叫了");
        }
    }
    public class Mouse
    {
        public void Run()
        {
            Console.WriteLine("老鼠跑了");
        }
    }
}

使用责任链模式实现(抽象方法)

using System;
using System.Collections.Generic;

namespace de2
{
    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat();
            cat.Add(new Dog());
            cat.Add(new Mouse());
            cat.AbsServer();
        }
    }

    public interface IAbsServer
    {
        void Do();
    }

    public class Cat : IAbsServer
    {
        private List<IAbsServer> list = new List<IAbsServer>();
        public void Add(IAbsServer absServer)
        {
            list.Add(absServer);
        }

        public void AbsServer()
        {
            this.Do();
            foreach (var item in list)
            {
                item.Do();
            }
        }

        public void Miao()
        {
            Console.WriteLine("猫叫了");
        }

        public void Do()
        {
            this.Miao();
        }
    }
    public class Dog : IAbsServer
    {
        public void Do()
        {
            this.Wang();
        }

        public void Wang()
        {
            Console.WriteLine("狗叫了");
        }
    }
    public class Mouse : IAbsServer
    {
        public void Do()
        {
            this.Run();
        }

        public void Run()
        {
            Console.WriteLine("老鼠跑了");
        }
    }
}

使用责任链模式实现(事件)

using System;

namespace de2
{
    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat();

            cat.miaoEvent += new Dog().Wang;
            cat.miaoEvent += new Mouse().Run;

            cat.MiaoEvent();
        }
    }

    public class Cat
    {
        public void Miao()
        {
            Console.WriteLine("猫叫了");
        }

        /// <summary>
        /// 事件,只能在事件所在类(本身类,子类不可)的内部 Invoke 执行
        /// </summary>
        public event Action miaoEvent;
        public void MiaoEvent()
        {
            this.Miao();
            this.miaoEvent.Invoke();
        }
    }
    public class Dog
    {
        public void Wang()
        {
            Console.WriteLine("狗叫了");
        }
    }
    public class Mouse
    {
        public void Run()
        {
            Console.WriteLine("老鼠跑了");
        }
    }
}

5.5 补充说明

链式委托的执行顺序是:按照委托链上的顺醋从当前委托开始依次往后执行,如果有需要可以使用GetInvocationList()方法来获得委托链上所有需要执行的委托,并且按照任何希望的顺序去逐个执行(Invoke

委托可以是带有返回值的方法,但多余一个带返回值的方法被添加到委托链中时,程序员需要手动地调用委托链上的每个方法,否则委托使用者智能得到委托链上最后一个被执行的方法的返回值

委托的应用场合通常是任务的执行者把细节工作进行再分配,执行者确切地知道什么工作将要被执行,但却把执行细节委托给其他组件、方法或者程序集

6. 委托事件

事件(Event):是委托的实例,在定义委托是加了enevt 关键字

enevt 关键字,限定权限,只能在事件所在类中调用事件

6.1 示例一:自定义标准事件

模拟:用户订阅手机降价事件,当降价时用于购买手机

using System;

namespace de3
{
    class Program
    {
        static void Main(string[] args)
        {
            Phone phone = new Phone
            {
                name = "vivo",
                Price = 1999
            };
            phone.DiscountEventHandler += new User() { name = "李白" }.Buy;

            phone.Price -= 400;
        }
    }

    // 事件额外信息
    public class EventPara
    {
        public int oValue { get; set; }
        public int nValue { get; set; }
    }

    public delegate void CostomEventHandler(object sender, EventPara para);

    // 手机,发布者,发布事件并且在满足条件情况下执行事件
    public class Phone
    {
        public string name { get; set; }
        private int price;
        public int Price
        {
            set
            {
                if (value < this.price)
                {
                    this.DiscountEventHandler?.Invoke(this, new EventPara
                    {
                        oValue = this.price,
                        nValue = value
                    });
                }
                this.price = value;
            }
            get { return this.price; }
        }

        public event CostomEventHandler DiscountEventHandler;
    }

    // 订户,关注事件,事件发生后执行动作
    public class User
    {
        public string name { get; set; }
        // 买手机
        public void Buy(object sender, EventPara para)
        {
            Phone phone = (Phone)sender;
            Console.WriteLine($"手机:{phone.name}\\t打折前:{para.oValue}\\t打折后:{para.nValue}");
            Console.WriteLine("购买手机!");
        }
    }
}

标注:委托事件实际应用还不太熟,示例做参考即可

7. 扩展补充

7.1 委托的内部结构

IL语言的无参无返回值的委托结构(编译后)

.class nested public auto ansi sealed NoReturnNoPare
    extends [mscorlib]System.MulticastDelegate
{
    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor (
            object \'object\',
            native int \'method\'
        ) runtime managed 
    {
    } // end of method NoReturnNoPare::.ctor

    .method public hidebysig newslot virtual 
        instance void Invoke () runtime managed 
    {
    } // end of method NoReturnNoPare::Invoke

    .method public hidebysig newslot virtual 
        instance class [mscorlib]System.IAsyncResult BeginInvoke (
            class [mscorlib]System.AsyncCallback callback,
            object \'object\'
        ) runtime managed 
    {
    } // end of method NoReturnNoPare::BeginInvoke

    .method public hidebysig newslot virtual 
        instance void EndInvoke (
            class [mscorlib]System.IAsyncResult result
        ) runtime managed 
    {
    } // end of method NoReturnNoPare::EndInvoke

} // end of class NoReturnNoPare

7.2 调用委托

使用委托实例调用,参数写在括号中

NoReturnNoPare d1 = new NoReturnNoPare(this.Show);
d1();

使用实例的Invoke()方法调用,参数写在方法中

NoReturnNoPare d1 = new NoReturnNoPare(this.Show);
d1.Invoke();

7.3 Predicate<T>委托

说明:不常用,仅作为了解(看个人情况)

Predicate是返回bool型的泛型委托,至少1个参数,至多1个参数,返回值固定为bool

官方示例

using System;
using System.Drawing;

public class Example
{
   public static void Main()
   {
      Point[] points = { new Point(100, 200),
                         new Point(150, 250), new Point(250, 375),
                         new Point(275, 395), new Point(295, 450) };

      Predicate<Point> predicate = FindPoints;
      Point first = Array.Find(points, predicate);
       
      Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y);
   }

   private static bool FindPoints(Point obj)
   {
      return obj.X * obj.Y > 100000;
   }
}
到达胜利之前无法回头!

以上是关于04.委托Delegation的主要内容,如果未能解决你的问题,请参考以下文章

事件委托(event delegation) 或叫 事件代理

以对象某属性为依据,为其List使用sort方法进行排序的一种基于委托(delegation)的实现方式

Mina中的stake delegation

unwind segue和delegation的区别

UIView 需要推送一个 UIViewController(委托)

对“xxx”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。 错误解决一例。(代码片段