04.委託Delegation

位永光發表於2021-08-08

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;
   }
}

相關文章