C#委託詳細理解

可以理解爲 把方法當成方法的參數

public void HelloWorld(string name, 方法 方法名)

C#的委託可理解爲函數的一個包裝,使得C#中的函數可以作爲參數進行傳遞,作用上相當於C/C++中的函數指針。如果函數指針想要指向某函數,函數指針的返回值類型和指向的函數的返回值類型必須相同,並且參數相同。

委託delegate是函數指針的升級版,函數指針是C/C++語言中特有的功能。

$ vim delegate.c
#include <stdio.h>
#include <stdlib.h>

//定義函數指針類型
typedef int(*Calc)(int x, int y);

int add(int x, int y)
{
    return x+y;
}

int sub(int x, int y){
    return x-y;
}

int main()
{
    int x = 100;
    int y = 200;
    int z = 0;

    //函數調用,直接調用。
    z = add(x,y);
    printf("%d + %d = %d\n", x, y, z);

    z = sub(x,y);
    printf("%d - %d = %d\n", x, y, z);

    //定義函數指針,間接調用。
    Calc fnp1 = &add;
    Calc fnp2 = &sub;
    
    z = fnp1(x, y);
    printf("%d + %d = %d\n", x, y, z);

    z = fnp2(x, y);
    printf("%d + %d = %d\n", x, y, z);

    system("pause");
    return 0;
}

$ gcc delegate.c -o delegate.exe
$ delegate.exe
100 + 200 = 300
100 - 200 = -100
100 + 200 = 300
100 + 200 = -100

定義委託:

使用委託首先要定義委託,聲明委託能代理什麼類型的方法,類似房產中介能代理抵押貸款業務,而不能代理打官司一樣。委託的定義和方法的定義類似,只是在定義前多了一個delegate關鍵字。

定義委託的語法:

<訪問修飾符> delegate 返回類型 委託名稱();

示例:

public delegate void DelagateName(int intParam, string strParam)

委託能包裝的方法有一定限制,委託類型DelegateName包裝的方法需滿足條件
(1)方法的返回值類型必須爲void。
(2)方法必須有兩個參數,第一個參數必須爲int類型,第二個參數必須爲string類型。

聲明委託:
如果要把方法當做參數來傳遞的話,就要用到委託。簡單來說,委託就是一個類型,這個類型可以賦值一個方法的引用。

C#中使用類分爲兩個階段,首先定義類,即告訴編譯器這個類由什麼字段和方法組成,然後使用類實例化後的對象。在使用委託時,也需要經歷兩個階段,首先定義委託,即告知編譯器這個委託可以指向那些類型的方法,然後創建該委託的實例。

使用delegate定義委託,定義委託需定義所要指向方法的參數和返回值。

//定義叫IntMethodInvoker的委託,指向帶有int類型參數的方法且返回值爲void。
delegate void IntMethodInvoker(int x);
delegate double TwoLongOp(long first, long second);
delegate string GetString();

使用委託:

using System;
namespace CSharp{
    class Program{
 
        //聲明委託:定義委託類型,委託類型名稱爲GetString
        private delegate string GetString();
        //入口主函數
        static void Main(string[] args){
    
        //使用委託調用ToString方法
        int x = 100;

        //聲明委託:使用委託類型創建實例
        //GetString method 使用委託聲明一個類型叫做method
        //new GetString(x.ToString)使用new對其進行初始化,使其引用到x中的ToSSString        ()上,這樣method就相當於x.ToString
        //ToString方法用來把數據轉換成字符串
        //GetString method = new GetString(x.ToString);
        
        //初始化委託:直接將方法賦值爲委託類型的變量
        GetString methed = x.ToString;

        //通過委託實例調用方法,通過Invoke方法調用method所引用的方法
        //string result = method.Invoke();

        //通過委託實例調用方法 簡寫
        //通過method()執行方法就相當於x.ToString()
        string result = method();
        Console.WriteLine(result);
        //100
        Console.ReadKey();
    }
  }
}

using System;
namespace CSharp
{
    class Program
    {
        /*定義委託*/
        private delegate void PrintString();
        /*使用委託作爲方法的參數*/
        static void PrintMethod(PrintString print)
        {
            print();
        }
        //靜態方法
        static void Method1()
        {
            Console.WriteLine("Method1");
        }
        //靜態方法
        static void Method2()
        {
            Console.WriteLine("Method2");
        }
        //入口主函數
        static void Main(string[] args)
        {
            //定義委託變量並初始化
            PrintString method = Method1;
            //將委託變量作爲方法參數
            PrintMethod(method); //Method1

            method = Method2;
            PrintMethod(method); //Method2         

            Console.ReadKey();
        }
    }
}

綜合案例

using System;

namespace Test
{
    class Operate
    {
        /*數組按降序排序*/
        public static bool ArraySort(int[] arr)
        {
            for(int i=arr.GetUpperBound(0); i>=0; i--)
            {
                for(int j=0; j<i; j++)
                {
                    if(arr[j] <= arr[i])
                    {
                        Swap(ref arr[j], ref arr[i]);
                    }
                }
            }
            return true;
        }
        /*交換兩個變量的值*/
        public static void Swap(ref int x, ref int y)
        {
            int tmp = x;
            x = y;
            y = tmp;
        }
    }
    class Program
    {
        /*定義委託*/
        public delegate bool DelegateSort(int[] arr);
        static void Main(string[] args)
        {
            int[] arr = new int[] { 3, 2, 9, 2, 4, 1 };

            Console.WriteLine("排序前");
            foreach (int i in arr)
            {
                Console.WriteLine(i);
            }

            //聲明委託變量
            DelegateSort delegateSort;
            //實例化委託,委託Operate的ArraySort排序
            delegateSort = new DelegateSort(Operate.ArraySort);
            //傳遞參數,調用委託進行排序
            delegateSort(arr);

            Console.WriteLine("排序後");
            foreach (int i in arr)
            {
                Console.WriteLine(i);
            }

            Console.ReadKey();
        }    
    }
}

一切皆地址,程序的本質是數據+算法。

(1)變量(數據)是以某個地址爲起點的一段內存中所存儲的值

(2)函數(算法)是以某個地址爲起點的一段內存中所存儲的一組機器語言指令

函數的直接調用間接調用

直接調用:
通過函數名來調用函數,CPU通過函數名直接獲得函數所在地址並開始執行並返回。

間接調用:

通過函數指針來調用函數,CPU通過讀取函數指針存儲的值來獲取函數所在地址並開始執行並返回。

Java中沒有與委託相對應的功能

預定義委託類型

(1)Action 委託

(2)Func 委託

除了自定義的委託之外,系統給我們提供了內置的委託類型Action和Func。

Action委託是系統內置預定義的,無返回值的泛型類型。其方法可通泛型來實現。

using System;
namespace CSharp{
    class Program{
        //無參數且無返回值的方法
        static void Print(){
            Console.WriteLine("hello");
    
    }
    //入口主函數
    static void Main(string[] args){
        //Action是系統預定義的一個委託類型
        //Action指向無參數且無返回值的方法
        Action action = Print;
        action();
        Console.ReadKey();
  
    }
  }
}

Action委託引用了一個返回值爲void的方法,其中T表示方法參數。

Action
Action<in T>
Action<in T1, in T2>
Action<in T1, in T2... in T6>
using System;

namespace CSharp
{
    class Program
    {       
        //無參數且無返回值的方法
        static void Print()
        {
            Console.WriteLine("hello");
        }
        //有參數無返回值的方法
        static void Print(string message)
        {
            Console.WriteLine(message);
        }
        static void Print(int x, int y)
        {
            Console.WriteLine(x+y);
        }
        //入口主函數
        static void Main(string[] args)
        {
            //Action是系統預定義的一個委託類型,默認指向無參數且無返回值的方法。
            Action action = Print;
            action();//hello

            //泛型委託:定義委託類型,此類型可以指向一個沒有返回值,但帶有一個參數的方法。
            Action<string> act = Print;
            act("world");//world

            //Action可通過泛型T去指定 Action指向的方法的多個參數的類型,參數類型跟action後面聲明的委託類型需保持一致。
            Action<int, int> a = Print;
            a(100, 200);

            Console.ReadKey();
        }
    }
}

Func委託引用了一個帶有返回值的方法,可傳遞0或多達16個參數類型,和一個返回類型。

Func<out TResult>
Func<in T, out TResult>
Func<in T1, in T2... in T16, out TResult>
using System;

namespace Test
{
    class Calculator
    {
        public void Report()
        {
            Console.WriteLine("I have 3 methods");
        }
        public int Add(int x, int y)
        {
            return x + y;
        }
        public int Sub(int x, int y)
        {
            return x - y;
        }
    }
    class Program
    {    
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            //直接調用
            calculator.Report();

            //Action委託類型
            Action action = new Action(calculator.Report);
            //間接調用
            action.Invoke();
            //簡寫
            action();

            //Function泛型委託
            Func<int, int, int> fn1 = new Func<int, int, int>(calculator.Add);
            Func<int, int, int> fn2 = new Func<int, int, int>(calculator.Sub);

            int x = 100;
            int y = 200;
            int z = 0;

            //z = fn1.Invoke(x, y);
            z = fn1(x, y);
            Console.WriteLine("{0} + {1} = {2}", x, y, z);

            //z = fn2.Invoke(x, y);
            z = fn2(x, y);
            Console.WriteLine("{0} - {1} = {2}", x, y, z);

            Console.ReadKey();
        }
    }
}

委託的聲明(自定義委託)

(1)委託是一種類class,類是數據類型,所以委託也是一種數據類型

Type t = typeof(Action);
Console.WriteLine(t.IsClass);//true

(2)委託的聲明方式與一般的類不同,主要是爲了照顧可讀性和C/C++傳統

(3)注意聲明委託的位置,避免寫錯地方,結果聲明成嵌套類型

(4)委託與所封裝的方法必須“類型兼容”

delegate double Calc[double x,double y];
        double Add[double x, double y]{return x + y;}
        double Sub[double x, double y]{return x - y;}
        double Mul[double x, double y]{return x * y;}
        double Div[double x, double y]{return x / y;}

(1)返回值的數據類型一致
(2)參數列表在個數和數據類型上一致(參數名不需要一樣)

using System;

namespace Test
{
    //自定義類
    class Calculator
    {
        public double Add(double x, double y)
        {
            return x + y;
        }
        public double Sub(double x, double y)
        {
            return x - y;
        }
        public double Mul(double x, double y)
        {
            return x * y;
        }
        public double Div(double x, double y)
        {
            return x / y;
        }
    }
    //自定義委託
    public delegate double Calc(double x, double y);

    class Program
    {    
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            //使用委託間接調用
            Calc calc = new Calc(calculator.Add);
            double x = 100.00;
            double y = 200.00;
            double z = 0.00;
            //z = calc.Invoke(x, y);
            //簡寫
            z = calc(x, y);

            Console.WriteLine(z);

            Console.ReadKey();
        }
    }
}

委託的使用
將方法當作參數傳遞給另一個方法
(1)模板方法 借用指定的外部方法來產生結果
相當於填空題,常位於代碼中部,委託有返回值。

using System;

namespace Test
{
    //產品
    class Product
    {
        public string Name { get; set; }
    }
    //包裝
    class Box
    {
        public Product Product { get; set; }
    }
    //包裝工廠
    class WrapFactory
    {
        //使用Func委託 模板方法
        public Box WrapProduct(Func<Product> getProduct)
        {
            Box box = new Box();
            Product product = getProduct.Invoke();
            box.Product = product;
            return box;
        }
    }
    //生產工廠
    class ProductFactory
    {
        public Product Make()
        {
            Product product = new Product();
            product.Name = "Pizza";
            return product;
        }
        public Product Build()
        {
            Product product = new Product();
            product.Name = "Car";
            return product;
        }
    }
    class Program
    {    
        static void Main(string[] args)
        {
            ProductFactory productFactory = new ProductFactory();
            WrapFactory wrapFactory = new WrapFactory();

            //使用模板方法
            Func<Product> fn1 = new Func<Product>(productFactory.Make);
            Func<Product> fn2 = new Func<Product>(productFactory.Build);

            Box box1 = wrapFactory.WrapProduct(fn1);
            Box box2 = wrapFactory.WrapProduct(fn2);

            Console.WriteLine(box1.Product.Name);
            Console.WriteLine(box2.Product.Name);

            Console.ReadKey();
        }
    }
}

(2)回調方法callback 調用指定的外部方法相當於流水線,常位於代碼末尾,委託無返回值。
(3)注意:委託難精通、易使用且功能強大,一旦被濫用後果非常嚴重。

  • 委託造成方法級別的緊耦合,現實工作中要慎之又慎。
  • 委託造成代碼可讀性下降,debug難度增加。
  • 將委託回調、異步調用、多線程糾纏在一起,使得代碼難以閱讀和維護。
  • 委託使用不當有可能造成內存泄漏和程序性能下降。
發佈了50 篇原創文章 · 獲贊 8 · 訪問量 4986
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章