Unity&C#的委託事件總結
阿新 • • 發佈:2019-02-20
吐血整理。。基本涵蓋了常見的各種委託,下面放了程式碼可供參考
基礎知識: 釋出器(publisher) 是一個包含事件和委託定義的物件。事件和委託之間的聯絡也定義在這個物件中。釋出器(publisher)類的物件呼叫這個事件,並通知其他的物件。即下文中的Mom(釋出方) 訂閱器(subscriber) 是一個接受事件並提供事件處理程式的物件。在釋出器(publisher)類中的委託呼叫訂閱器(subscriber)類中的方法(事件處理程式)。即下文中的Mom,Dad(監聽方) 發出者sender:用於記錄呼叫事件的是哪個類 注意:我們通常將委託類和委託類的物件都成為委託,但是兩者是有區別的。一旦定義了委託類,基本上就可以例項化它的例項,在這一點上和普通類似一致的。即我們也可以有委託陣列 。
Action<T>和Func<T>委託
我們之前都是根據返回值型別和引數列表來定義委託類,然後在根據委託類來生成委託的例項。現在我們還可以使用泛型委託類Action<T>和Func<T>。
泛型Action<T>委託類表示引用一個void返回型別的方法,可以傳遞至多16個不同的引數型別。
Action<in T1,in T2, …,in Tn> (n最大為16,例如Action<in T1,in T2>就表示呼叫2個引數的方法)。Func<T>委託的使用方式和Action<T>委託類 似.Func<T>允許呼叫帶有返回值的方法。Func<in T1, in T2, ...,in Tn, out TResult> (n的最大值還是16,Func<in T1, in T2, out TResult>表示呼叫兩個引數的方法且返回值型別為TResult)。
我們用Func<T>委託來實現上述委託:
Func<int, int, int> TestDele = add;
這一條語句就等價於委託類的宣告和委託物件的建立。同理,Action<T>也是一樣的用法。而且功能上沒有任何的不同。唯一不足之處就是引數的個數是有限制的,不過大多數的情況下16個的引數已經足夠使用了。
Calc TestDele = delegate(int a,int b){ //程式碼塊(因為Calc委託類是有返回值的,所以函式體內必須有return語句)}; //這裡有一個分號,千萬不能漏
通過使用匿名方法,由於不必建立單獨的方法,因此減少了例項化委託所需的編碼系統開銷。而且使用匿名方法可以有效減少要編寫的程式碼,有助於降低程式碼的複雜度。
然而我們在使用匿名委託的時候我們要遵守兩個原則:1、匿名方法中不能有跳轉語句(break, goto或continue)跳轉到匿名方法的外部,反之,外部程式碼也不能跳轉到該匿名方法內部。2、在匿名方法中不能訪問不安全程式碼。
注意:不能訪問在匿名方法外部使用的ref和out引數。
[委託類] [委託物件名] = ( [引數列表] ) => { /*程式碼塊*/ }; //結尾還是有一個分號
我們值得注意的是lambda表示式的引數列表,我們只需給出變數名即可,其餘的編譯器會自動和委託類進行匹配。如果委託類使用返回值的,那麼程式碼塊就需要return一個返回值。我們用一個例子來說明上述問題:
Func<int, int> TestLam = (x) => { return x*x; };
在這裡我們是使用一個引數的為例,上面的寫法是Lambda表示式的正常寫法,但是當引數只有一個時,x兩邊的括號就可以去除,那麼現在程式碼就變成這樣了:
Func<int, int> TestLam = x => { return x*x; };
當Lambda表示式程式碼塊中只有一條語句,那麼我們就可以把花括號丟了。如果這一條語句還是包含return的語句,那麼我們在去除花括號的同時,必須將return同時刪去。現在上述程式碼就變成了這樣:
Func<int, int> TestLam = x => x*x;
注意:Lambda表示式可以用於型別為委託的任意地方。
3、閉包 通過lambda表示式可以訪問lambda表示式外部的變數,於是我們就引出了一個新的概念-----閉包。我們來看一個例子: int someVal = 5;Func<int, int> f = x => x+someVal; 現在我們很容易知道f(3)的返回值是8,我們繼續:
someVal = 7;
我們現在將someVal的值改為7,那麼這時我們在呼叫f(3),現在就會很神奇的發現f(3)的返回值變成了10。這就是閉包的 特點,這個特點在程式設計上很大程度上能給我們帶來一定的好處。但是有利終有弊,如果我們使用不當,那麼這就變成了一個非常危險的功能。
我現在再來看看在foreach語句中的閉包,我們現在看看下面這段程式碼: List<int> values = new List<int>() { 10, 20, 30 }; var funcs = new List<Func<int>>(); foreach (var val in values) { funcs.Add(() => val);} foreach (var f in funcs) { Console.WriteLine(f());} 用我們剛才的知識來判斷的話,輸出結果應該是3個30。然而在C#4.0確實是這樣,然而C#5.0會在foreach建立的while迴圈的程式碼塊中建立一個不同的區域性迴圈變數,所以這時在C#5.0中我們輸出的結果應該是分別輸出10,20和30。
附C++的函式指標用法
Output:
now call max(10,55)...
55
now call min(10,55)... 10
基礎知識: 釋出器(publisher) 是一個包含事件和委託定義的物件。事件和委託之間的聯絡也定義在這個物件中。釋出器(publisher)類的物件呼叫這個事件,並通知其他的物件。即下文中的Mom(釋出方) 訂閱器(subscriber) 是一個接受事件並提供事件處理程式的物件。在釋出器(publisher)類中的委託呼叫訂閱器(subscriber)類中的方法(事件處理程式)。即下文中的Mom,Dad(監聽方) 發出者sender:用於記錄呼叫事件的是哪個類 注意:我們通常將委託類和委託類的物件都成為委託,但是兩者是有區別的。一旦定義了委託類,基本上就可以例項化它的例項,在這一點上和普通類似一致的。即我們也可以有委託陣列
-
UnityAction本質上是delegate,且有數個泛型版本(引數最多是4個),一個UnityAction可以新增多個函式(多播委託)
- UnityEvent本質上是繼承自UnityEventBase的類,它的AddListener()方法能夠註冊UnityAction,是臨時的
- RemoveListener能夠取消註冊UnityAction,
- 還有Invoke()方法能夠一次性呼叫所有註冊了的UnityAction。
- UnityEvent也有數個泛型版本(引數最多也是4個),但要注意的一點是,UnityAction的所有帶引數的泛型版本都是抽象類(abstract),所以如果要使用的話,需要自己宣告一個類繼承之,然後再例項化該類才可以使用。
Unity中通過面板中新增的Listener和通過指令碼新增的Listener實際上是兩種不同型別的Listener:
- 在指令碼中通過AddListener()新增的是一個0個引數的delegate(UnityAction)回撥。是不可序列化的,在Inspector中是無法看到的。這種Listener是常規Listener。
- 在Inspector中新增的則是永久性的Listener(persistent listener)。他們需要指定GameObject、方法以及方法需要的引數。他們是序列化的,用指令碼是無法訪問到的。
1、匿名委託
在這之前我們使用委託那麼都必須先有一個方法。那麼現在我們可以通過另一種方式使委託工作:匿名方法。用匿名方法來實現委託和之前的定義並沒有太大的區別,唯一不同之處就在於例項化。我們就以之前Calc委託類為例:Calc TestDele = delegate(int a,int b){ //程式碼塊(因為Calc委託類是有返回值的,所以函式體內必須有return語句)}; //這裡有一個分號,千萬不能漏
通過使用匿名方法,由於不必建立單獨的方法,因此減少了例項化委託所需的編碼系統開銷。而且使用匿名方法可以有效減少要編寫的程式碼,有助於降低程式碼的複雜度。
然而我們在使用匿名委託的時候我們要遵守兩個原則:1、匿名方法中不能有跳轉語句(break, goto或continue)跳轉到匿名方法的外部,反之,外部程式碼也不能跳轉到該匿名方法內部。2、在匿名方法中不能訪問不安全程式碼。
注意:不能訪問在匿名方法外部使用的ref和out引數。
2、Lambda表示式
由於Lambda表示式的出現使得我們的程式碼可以變得更加的簡潔明瞭。我們現在來看看Lambda表示式的使用語法:[委託類] [委託物件名] = ( [引數列表] ) => { /*程式碼塊*/ }; //結尾還是有一個分號
我們值得注意的是lambda表示式的引數列表,我們只需給出變數名即可,其餘的編譯器會自動和委託類進行匹配。如果委託類使用返回值的,那麼程式碼塊就需要return一個返回值。我們用一個例子來說明上述問題:
Func<int, int> TestLam = (x) => { return x*x; };
在這裡我們是使用一個引數的為例,上面的寫法是Lambda表示式的正常寫法,但是當引數只有一個時,x兩邊的括號就可以去除,那麼現在程式碼就變成這樣了:
Func<int, int> TestLam = x => { return x*x; };
當Lambda表示式程式碼塊中只有一條語句,那麼我們就可以把花括號丟了。如果這一條語句還是包含return的語句,那麼我們在去除花括號的同時,必須將return同時刪去。現在上述程式碼就變成了這樣:
Func<int, int> TestLam = x => x*x;
注意:Lambda表示式可以用於型別為委託的任意地方。
3、閉包 通過lambda表示式可以訪問lambda表示式外部的變數,於是我們就引出了一個新的概念-----閉包。我們來看一個例子: int someVal = 5;Func<int, int> f = x => x+someVal; 現在我們很容易知道f(3)的返回值是8,我們繼續:
someVal = 7;
我們現在將someVal的值改為7,那麼這時我們在呼叫f(3),現在就會很神奇的發現f(3)的返回值變成了10。這就是閉包的 特點,這個特點在程式設計上很大程度上能給我們帶來一定的好處。但是有利終有弊,如果我們使用不當,那麼這就變成了一個非常危險的功能。
我現在再來看看在foreach語句中的閉包,我們現在看看下面這段程式碼: List<int> values = new List<int>() { 10, 20, 30 }; var funcs = new List<Func<int>>(); foreach (var val in values) { funcs.Add(() => val);} foreach (var f in funcs) { Console.WriteLine(f());} 用我們剛才的知識來判斷的話,輸出結果應該是3個30。然而在C#4.0確實是這樣,然而C#5.0會在foreach建立的while迴圈的程式碼塊中建立一個不同的區域性迴圈變數,所以這時在C#5.0中我們輸出的結果應該是分別輸出10,20和30。
using UnityEngine;
using System;//EventArgs需要
using UnityEngine.Events;//Unity事件需要
using UnityEngine.UI;//展示註冊已有的UI事件
public class Mom : MonoBehaviour {
//本程式碼是以總結順序寫的,宣告-定義-註冊-呼叫。
//用法1:一個物件可以一口氣執行多個方法,並且可順序執行其他類的方法,用於監聽-執行
//1.宣告一個C#委託協定,指定方法簽名
public delegate int MyDelegate(int i, int j);
//委託也是一個型別,需要宣告物件
MyDelegate myDelegate;
//2.C#事件的宣告:event+委託+事件名
public event MyDelegate CSharpEvent;
//事件就是一個委託物件,而委託可以是一種型別
//3.C#自身提供了一種比較好的委託型別:EventHandler
//sender表示事件源,e表示事件相關的資訊
public delegate void EventHandler(object sender, EventArgs e);
EventHandler CSharpDelegateHandler;
//4.另一種更好的委託方式是使用泛型引數的委託型別:EventHandler<TEventArgs>,其簽名如下:
//如果產生額外引數,第二個引數是從 EventArgs 派生的型別並提供所有欄位或屬性需要儲存事件資料。使用 EventHandler<TEventArgs> 的優點在於,如果事件生成事件資料,則無需編寫自己的自定義委託程式碼。
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
//5.我們也可以為這些委託宣告事件(EventHandler事件處理器就是事件)
public event EventHandler CSharpEventHandler;
//6.使用UnityEvent(可視) 和 UnityAction(不可視)
//使用類建立的話一條語句就等價於委託類的宣告和委託物件的建立
public UnityAction unityAction;//本質是delegate
public UnityEvent unityEvent = new UnityEvent();//也可以在定義時new。本質上是繼承自UnityEventBase的類
//7.Unity泛型委託
//因為UnityEvent<T0>是抽象類,所以需要宣告一個類來繼承它
public class UnityActionWithParameter: UnityEvent<int>{
//巢狀類
}
public UnityActionWithParameter unityActionWithParameter = new UnityActionWithParameter();
public UnityAction<int> unityIntAction;
public UnityEvent<int,int> unityIntEvent;
//8.泛型委託Action<T>和Func<T>
public Action<int, int> CSharpIntAction;//使用類建立的話一條語句就等價於委託類的宣告和委託物件的建立
public Func<int, int, int> CSharpIntFunc;//最後一個int是函式返回型別
//註冊的方法要求:返回型別+引數的型別+引數個數 引數名可以不同
int Add(int a, int b)
{
return a + b;
}
int Subtarct(int a,int b)
{
return a - b;
}
void NoParameter()
{
//無參方法
}
void OneIntParameter(int i)
{
//一個int引數,且返回void
}
void TwoIntParameter(int a,int b)
{
//2個int引數,且返回void
}
//針對EventHandler的註冊方法
void OnGameStart(object sender, EventArgs e)
{
//你要執行的程式碼
}
//用法2:委託充當函式指標
static void functionPointer(MyDelegate function,int a,int b)
{
function(a, b);
}
void Start()
{
//委託物件的初始化和賦值 和普通類一致。注意由於宣告的委託不包含0參建構函式,所以必須要註冊引數
myDelegate =new MyDelegate(Add);
//C#事件新增方法
CSharpEvent += new MyDelegate(Add);
//其實這幾個無論用=,+=都是可以的
CSharpDelegateHandler = new EventHandler(OnGameStart);
//另外還有其他的註冊方式,這裡就拿C#標準委託的事件為例
CSharpEventHandler += OnGameStart;
CSharpEventHandler -= OnGameStart;//當監聽到後不需要執行這個方法時(取消監聽)
CSharpEvent += Add;//同樣可以
Dad dad = new Dad();
CSharpEvent += dad.Add;//如果我們想註冊外部類的方法也是可以的(注意要public)
unityAction = new UnityAction(NoParameter);
unityAction += NoParameter;
//注意UnityEvent只能用AddListener臨時註冊方法
unityEvent.AddListener(NoParameter);
unityEvent.RemoveListener(NoParameter);
unityIntAction += OneIntParameter;//可以使用+=
unityIntAction = new UnityAction<int>(OneIntParameter);//註冊要求一個int引數,且返回void
unityActionWithParameter.AddListener(OneIntParameter);//其實我不確定上面那個能不能用,例項中是隻用了這行的事件的
unityIntEvent.AddListener(TwoIntParameter);//註冊要求2個int引數,且返回void
CSharpIntAction += TwoIntParameter;//可以使用+=
CSharpIntAction = new Action<int, int>(TwoIntParameter);
CSharpIntFunc += Add;//要求返回int型別
CSharpIntFunc = new Func<int, int, int>(Add);
//9.lambda方式可以新增包含任意引數的函式,非常方便。以下是為onClick事件註冊方法
GetComponent<Button>().onClick.AddListener(() =>
{//意思是用(引數列表)執行以下函式體的內容
//你要執行的程式碼,也可以執行方法,可以使用引數
});
//以下是呼叫。一般放在監聽成功的位置
//C#委託的呼叫:委託物件及引數
myDelegate(1, 2);
myDelegate.Invoke(1,2);//呼叫與上面等價
//C#事件的呼叫,同委託完全一樣
CSharpEvent(1, 2);
CSharpEvent.Invoke(2, 4);
//C#標準委託呼叫,注意不需任何引數時就這樣
if(CSharpDelegateHandler!=null)//可以加個安全檢查
CSharpDelegateHandler(this, EventArgs.Empty);
unityAction();//無參呼叫
unityAction.Invoke();
unityEvent.Invoke();//UnityEvent只能使用Invoke()呼叫
unityIntAction(1);//1個int引數
unityActionWithParameter.Invoke(1);//繼承UnityAction<int>的事件
unityIntEvent.Invoke(1,2);//在Invoke中提供實參
CSharpIntAction(1, 2);//Invoke就省略不寫了
CSharpIntFunc(1, 2);
//函式指標的使用
functionPointer(myDelegate, 2, 4);//可以用已有的委託物件做引數
functionPointer(new MyDelegate(Subtarct), 4, 2);//也可以new一個新物件
//另外如果物件是public的那麼我們也可以在外部類執行以上程式碼
}
}
附C++的函式指標用法
//這些不用看
//Platform: WinXP + VC6.0
#include
<iostream.h>
|
55
now call min(10,55)... 10