C# 函数扩展方法的妙用

  扩展方法 Extension Method 我们很多时候都是考虑方便性才去添加的, 系统库也有很多, 像 Linq / Expression 之类的, 使用起来就像是给对象添加了一个成员函数一样 : 

官方例子

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

  看到扩展的函数调用就像成员变量一样, 不过真的是这样吗? 看看下面的代码 : 

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

  断点看, 能够进来 : 

  那么它提供的扩展就不是代码层面的, 是编译层面的了, 在编译上所有的方法都是静态的, 只是在调用的时候传入了调用对象, 而成员函数只是在上面进行的封装, 从反射的Method.Invoke() 就能看到实例需要传入对象才能正确调用 : 

public object Invoke(object obj, object[] parameters)

  其实它的代码等效于 : 

aCall.Call();
// 等于
Ext.Call(aCall);

  所以就算看起来是成员函数调用, 其实是静态调用, 所以即使对象 aCall 是空, 也是可以运行的, 对于 Unity 来说, 很多时候会发生非预期的对象删除, 或者删除后仍然存在的现象, 每次都需要判空, 就像一个 UI 上的 Text 这样 : 

public class Test : MonoBehaviour
{    
    public Text title;
    public void SetTitle(string info)
    {
        if(title)
        {
            title.text = info;
        }
    }
}

  这样只在功能内写判空的就比较累人, 不如写个静态方法 : 

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");

  不过现在发现扩展方法的调用也是静态调用, 空对象也能运行, 那就写成扩展就更方便了 : 

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 来判断, 想要这个功能的正确实现, 只有通过修改语法树的方法来尝试了...

 

  补充个常用方案:

    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static void SetText_W(this Text text, string info)
    {
#if UNITY_EDITOR
        text.text = info;
#else
        if(text)
        {
            text.text = info;
        }
#endif
    }

  像这样的, 建议编译器进行inline编译, 可以减少函数开销, 然后在开发阶段还是会报错, 能够在开发时候发现错误, 不过在发布之后就是带有检测的代码, 就算出错也不影响代码执行.

posted @ 2020-08-05 11:03  tiancaiKG  阅读(678)  评论(0编辑  收藏  举报