委托
如果我們要把方法當做參數來傳遞的話,就要用到委托。簡單來說委托是一個類型,這個類型可以賦值一個方法的引用。
聲明委托
在C#中使用一個類分兩個階段,首選定義這個類,告訴編譯器這個類由什么字段和方法組成的,然后使用這個類實例化對象。在我們使用委托的時候,也需要經過這兩個階段,首先定義委托,告訴編譯器我們這個委托可以指向哪些類型的方法,然后,創建該委托的實例。
定義委托的語法如下:
delegate void IntMethodInvoker(int x);
定義了一個委托叫做IntMethodInvoker, 這個方法要帶有一個int類型的參數,並且方法的返回值是void的。 定義一個委托要定義方法的參數和返回值,使用關鍵字delegate定義。
使用委托
private delegate string GetAString();
static void Main(){
int x = 40;
GetAString firstStringMethod = new GetAString(x.ToString);
Console.WriteLine(firstStringMethod());
}
在這里我們首先使用GetAString委托聲明了一個類型叫做fristStringMethod,接下來使用new 對它進行初始化,使它引用到x中的ToString方法上,這樣firstStringMethod就相當於x.ToString,我們通過firstStringMethod()執行方法就相當於x.ToString()
通過委托示例調用方法有兩種方式
fristStringMethod();
firstStringMethod.Invoke();
委托的賦值
只需要把方法名給一個委托的構造方法就可以了
GetAString firstStringMethod = new GetAString(x.ToString);
也可以把方法名直接給委托的實例
GetAString firstStringMethod = x.ToString;
Action委托和Func委托
除了我們自己定義的委托之外,系統還給我們提供過來一個內置的委托類型,Action和Func
Action委托引用了一個void返回類型的方法,T表示方法參數
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _005_Action委托 {
class Program {
static void PrintString()
{
Console.WriteLine("hello world.");
}
static void PrintInt(int i)
{
Console.WriteLine(i);
}
static void PrintString(string str)
{
Console.WriteLine(str);
}
static void PrintDoubleInt(int i1, int i2)
{
Console.WriteLine(i1+i2);
}
static void Main(string[] args)
{
//Action a = PrintString;//action是系統內置(預定義)的一個委托類型,它可以指向一個沒有返回值,沒有參數的方法
//Action<int> a=PrintInt;//定義了一個委托類型,這個類型可以指向一個沒有返回值,有一個int參數的方法
//Action<string> a = PrintString;//定義了一個委托類型,這個類型可以指向一個沒有返回值,有一個string參數的方法 在這里系統會自動尋找匹配的方法
Action<int, int> a = PrintDoubleInt;
a(34, 23);
Console.ReadKey();
//action可以后面通過泛型去指定action指向的方法的多個參數的類型 ,參數的類型跟action后面聲明的委托類型是對應着的
}
}
}
Func引用了一個帶有一個返回值的方法,它可以傳遞0或者多到16個參數類型,和一個返回類型
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _006_Func委托 {
class Program {
static int Test1()
{
return 1;
}
static int Test2(string str)
{
Console.WriteLine(str);
return 100;
}
static int Test3(int i, int j)
{
return i + j;
}
static void Main(string[] args)
{
//Func<int> a = Test1;//func中的泛型類型制定的是 方法的返回值類型
//Console.WriteLine(a());
//Func<string, int> a = Test2;//func后面可以跟很多類型,最后一個類型是返回值類型,前面的類型是參數類型,參數類型必須跟指向的方法的參數類型按照順序對應
Func<int, int, int> a = Test3;//func后面必須指定一個返回值類型,參數類型可以有0-16個,先寫參數類型,最后一個是返回值類型
int res = a(1, 5);
Console.WriteLine(res);
Console.ReadKey();
}
}
}
多播委托
前面使用的委托都只包含一個方法的調用,但是委托也可以包含多個方法,這種委托叫做多播委托。使用多播委托就可以按照順序調用多個方法,多播委托只能得到調用的最后一個方法的結果,一般我們把多播委托的返回類型聲明為void。
Action action1 = Test1;
action2+=Test2;
action2-=Test1;
多播委托包含一個逐個調用的委托集合,如果通過委托調用的其中一個方法拋出異常,整個迭代就會停止。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _008_多播委托 {
class Program {
static void Test1()
{
Console.WriteLine("test1");
//throw new Exception();
}
static void Test2()
{
Console.WriteLine("test2");
}
static void Main(string[] args) {
//多播委托
Action a = Test1;
a += Test2;//表示添加一個委托的引用
//a -= Test1;
//a -= Test2;
//if(a!=null)
// a();//當一個委托沒有指向任何方法的時候,調用的話會出現異常null
// 取得多播委托中所有方法的委托
Delegate[] delegates = a.GetInvocationList();
foreach (Delegate de in delegates)
{
de.DynamicInvoke();
}
Console.ReadKey();
}
}
}
匿名方法
到目前為止,使用委托,都是先定義一個方法,然后把方法給委托的實例。但還有另外一種使用委托的方式,不用去定義一個方法,應該說是使用匿名方法(方法沒有名字)。
Func<int,int,int> plus = delegate (int a,int b){
int temp = a+b;
return temp;
};
int res = plus(34,34);
Console.WriteLine(res);
在這里相當於直接把要引用的方法直接寫在了后面,優點是減少了要編寫的代碼,減少代碼的復雜性
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _009_匿名方法 {
class Program {
static int Test1(int arg1, int arg2)
{
return arg1 + arg2;
}
static void Main(string[] args)
{
//Func<int, int, int> plus = Test1;
//修改成匿名方法的形式
Func<int, int, int> plus = delegate(int arg1, int arg2)
{
return arg1 + arg2;
};
//匿名方法 本質上是一個方法,只是沒有名字,任何使用委托變量的地方都可以使用匿名方法賦值
Console.ReadKey();
}
}
}
Lambda表達式
從C#3.0開始,可以使用Lambda表達式代替匿名方法。只要有委托參數類型的地方就可以使用Lambda表達式。剛剛的例子可以修改為
Func<int,int,int> plus = (a,b)=>{ int temp= a+b;return temp; };
int res = plus(34,34);
Console.WriteLine(res);
Lambda運算符“=>”的左邊列出了需要的參數,如果是一個參數可以直接寫 a=>(參數名自己定義),如果多個參數就使用括號括起來,參數之間以,間隔。
如果Lambda表達式只有一條語句,在方法快內就不需要花括號和return語句,編譯器會自動添加return語句。如果Lambda表達式的實現代碼中需要多條語句,就必須添加花括號和return語句。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _010_Lambda表達式 {
class Program {
static void Main(string[] args) {
//lambda表達式用來代替匿名方法,所以一個lambda表達式也是定義了一個方法
//Func<int, int, int> plus = delegate(int arg1, int arg2) {
// return arg1 + arg2;
//};
//Func<int, int, int> plus = (arg1, arg2) =>// lambda表達式的參數是不需要聲明類型的
//{
// return arg1 + arg2;
//};
//Console.WriteLine(plus(90,60));
Func<int, int> test2 = a => a+1;//lambda表示的參數只有一個的時候,可以不加上括號 當函數體的語句只有一句的時候,我們可以不加上大括號 也可以不加上return語句
Func<int, int> test3 = (a) =>
{
return a + 1;
};
Console.WriteLine(test2(34));
Console.WriteLine(test3(34));
Console.ReadKey();
}
}
}
通過Lambda表達式可以訪問Lambda表達式塊外部的變量。這是一個非常好的功能,但如果不能正確使用,也會非常危險。示例:
int somVal = 5;
Func<int,int> f = x=>x+somVal;
Console.WriteLine(f(3));//8
somVal = 7;
Console.WriteLine(f(3));//10
這個方法的結果,不但受到參數的控制,還受到somVal變量的控制,結果不可控,容易出現編程問題,用的時候要謹慎。
事件
事件(event)基於委托,為委托提供了一個發布/訂閱機制,我們可以說事件是一種具有特殊簽名的委托。
比如:我們按鍵點擊,這就是一個事件,在其它地方接收到這個事件,觸發相應。
下面使用貓和老鼠的實例來演示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _012_觀察者設計模式_貓捉老鼠 {
/// <summary>
/// 貓類
/// </summary>
class Cat
{
private string name;
private string color;
public Cat(string name, string color)
{
this.name = name;
this.color = color;
}
/// <summary>
/// 貓進屋(貓的狀態發生改變)(被觀察者的狀態發生改變)
/// </summary>
public void CatComing()
{
Console.WriteLine(color+"的貓"+name+"過來了,喵喵喵 ...");
if(catCome!=null)
catCome();
}
public event Action catCome;//聲明一個事件 發布了一個消息
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _012_觀察者設計模式_貓捉老鼠 {
/// <summary>
/// 觀察者類:老鼠
/// </summary>
class Mouse
{
private string name;
private string color;
public Mouse(string name, string color,Cat cat)
{
this.name = name;
this.color = color;
cat.catCome += this.RunAway;//把自身的逃跑方法 注冊進 貓里面 訂閱消息
}
/// <summary>
/// 逃跑功能
/// </summary>
public void RunAway()
{
Console.WriteLine(color+"的老鼠"+name+"說: 老貓來, 趕緊跑, 我跑, 我使勁跑,我加速使勁跑 ...");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _012_觀察者設計模式_貓捉老鼠 {
class Program {
static void Main(string[] args) {
Cat cat = new Cat("加菲貓","黃色");
Mouse mouse1 = new Mouse("米奇","黑色",cat);
Mouse mouse2 = new Mouse("水", "紅色",cat);
cat.CatComing();
//cat.catCome();//事件不能再類的外部觸發,只能在類的內部觸發
Console.ReadKey();
}
}
}