擴展方法 Extension Method 咱們不少時候都是考慮方便性纔去添加的, 系統庫也有不少, 像 Linq / Expression 之類的, 使用起來就像是給對象添加了一個成員函數同樣 : 函數
官方例子this
namespace ExtensionMethods { public static class IntExtensions { public static bool IsGreaterThan(this int i, int value) { return i > value; } } }
using ExtensionMethods; class Program { static void Main(string[] args) { int i = 10; bool result = i.IsGreaterThan(100); Console.WriteLine(result); } }
看到擴展的函數調用就像成員變量同樣, 不過真的是這樣嗎? 看看下面的代碼 : spa
using UnityEngine; using System; public class Test : MonoBehaviour { private System.Action aCall = null; private void Start() { aCall.Call(); } } public static class Ext { public static void Call(this System.Action action) { if(action != null) { action.Invoke(); } } }
斷點看, 可以進來 : code
那麼它提供的擴展就不是代碼層面的, 是編譯層面的了, 在編譯上全部的方法都是靜態的, 只是在調用的時候傳入了調用對象, 而成員函數只是在上面進行的封裝, 從反射的Method.Invoke() 就能看到實例須要傳入對象才能正確調用 : 對象
public object Invoke(object obj, object[] parameters)
其實它的代碼等效於 : blog
aCall.Call(); // 等於 Ext.Call(aCall);
因此就算看起來是成員函數調用, 實際上是靜態調用, 因此即便對象 aCall 是空, 也是能夠運行的, 對於 Unity 來講, 不少時候會發生非預期的對象刪除, 或者刪除後仍然存在的現象, 每次都須要判空, 就像一個 UI 上的 Text 這樣 : string
public class Test : MonoBehaviour { public Text title; public void SetTitle(string info) { if(title) { title.text = info; } } }
這樣只在功能內寫判空的就比較累人, 不如寫個靜態方法 : it
public class Test : MonoBehaviour { public UnityEngine.UI.Text title; } public static class Ext { public static void SetTextUI(UnityEngine.UI.Text text, string info) { if(text) { text.text = info; } } } //... Text textUI; Ext.SetTextUI(textUI, "xxx");
不過如今發現擴展方法的調用也是靜態調用, 空對象也能運行, 那就寫成擴展就更方便了 : io
public static class Ext { public static void SetTextUI(this UnityEngine.UI.Text text, string info) { if(text) { text.text = info; } } } //... Text textUI; textUI.SetTextUI("xxx");
這就是擴展方法的好處了, 它不是代碼層面的添加了一個成員函數.編譯
還有一個如今用 C# 6.0 以上語法的話, 能夠直接判空 :
Text textUI; textUI?.text = "xxx";
但是對於 Unity Object 對象, 這樣的判空至關於 :
Text textUI; if(textUI != null) { textUI.text = "xxx"; }
這樣判空是不對的, 必須使用它的隱式轉換 bool 來判斷, 想要這個功能的正確實現, 只有經過修改語法樹的方法來嘗試了...