欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#實(shí)現(xiàn)接口base調(diào)用示例詳解

 更新時(shí)間:2022年06月10日 16:10:51   作者:返回主頁月光雙刀  
這篇文章主要為大家介紹了C#實(shí)現(xiàn)接口base調(diào)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

在三年前發(fā)布的C#8.0中有一項(xiàng)重要的改進(jìn)叫做接口默認(rèn)實(shí)現(xiàn),從此以后,接口中定義的方法可以包含方法體了,即默認(rèn)實(shí)現(xiàn)。

不過對(duì)于接口的默認(rèn)實(shí)現(xiàn),其實(shí)現(xiàn)類或者子接口在重寫這個(gè)方法的時(shí)候不能對(duì)其進(jìn)行base調(diào)用,就像子類重寫方法是可以進(jìn)行base.Method()那樣。例如:

public interface IService
{
    void Proccess()
    {
        Console.WriteLine("Proccessing");
    }
}
public class Service : IService
{
    public void Proccess()
    {
        Console.WriteLine("Before Proccess");
        base(IService).Proccess(); // 目前不支持,也是本文需要探討的部分
        Console.WriteLine("End Proccess");
    }
}

當(dāng)初C#團(tuán)隊(duì)將這個(gè)特性列為了下一步的計(jì)劃(點(diǎn)此查看細(xì)節(jié)),然而三年過去了依然沒有被提上日程。這個(gè)特性的缺失無疑是一種很大的限制,有時(shí)候我們確實(shí)需要接口的base調(diào)用來實(shí)現(xiàn)某些需求。本文將介紹兩種方法來實(shí)現(xiàn)它。

方法1:使用反射找到接口實(shí)現(xiàn)并進(jìn)行調(diào)用

這種方法的核心思想是,使用反射找到你需要調(diào)用的接口實(shí)現(xiàn)的MethodInfo,然后構(gòu)建DynamicMethod使用OpCodes.Call去調(diào)用它即可。

首先我們定義方法簽名用來表示接口方法的base調(diào)用。

public static void Base<TInterface>(this TInterface instance, Expression<Action<TInterface>> selector);
public static TReturn Base<TInterface, TReturn>(this TInterface instance, Expression<Func<TInterface, TReturn>> selector);

所以上一節(jié)的例子就可以改寫成:

public class Service : IService
{
    public void Proccess()
    {
        Console.WriteLine("Before Proccess");
        this.Base&lt;IService&gt;(m =&gt; m.Proccess());
        Console.WriteLine("End Proccess");
    }
}

于是接下來,我們就需要根據(jù)lambda表達(dá)式找到其對(duì)應(yīng)的接口實(shí)現(xiàn),然后調(diào)用即可。

第一步根據(jù)lambda表達(dá)式獲取MethodInfo和參數(shù)。要注意的是,對(duì)于屬性的調(diào)用我們也需要支持,其實(shí)屬性也是一種方法,所以可以一并處理。

private static (MethodInfo method, IReadOnlyList&lt;Expression&gt; args) GetMethodAndArguments(Expression exp) =&gt; exp switch
{
    LambdaExpression lambda =&gt; GetMethodAndArguments(lambda.Body),
    UnaryExpression unary =&gt; GetMethodAndArguments(unary.Operand),
    MethodCallExpression methodCall =&gt; (methodCall.Method!, methodCall.Arguments),
    MemberExpression { Member: PropertyInfo prop } =&gt; (prop.GetGetMethod(true) ?? throw new MissingMethodException($"No getter in propery {prop.Name}"), Array.Empty&lt;Expression&gt;()),
    _ =&gt; throw new InvalidOperationException("The expression refers to neither a method nor a readable property.")
};

第二步,利用Type.GetInterfaceMap獲取到需要調(diào)用的接口實(shí)現(xiàn)方法。此處注意的要點(diǎn)是,instanceType.GetInterfaceMap(interfaceType).InterfaceMethods 會(huì)返回該接口的所有方法,所以不能僅根據(jù)方法名去匹配,因?yàn)榭赡苡懈鞣N重載、泛型參數(shù)、還有new關(guān)鍵字聲明的同名方法,所以可以按照方法名+聲明類型+方法參數(shù)+方法泛型參數(shù)唯一確定一個(gè)方法(即下面代碼塊中IfMatch的實(shí)現(xiàn))

internal readonly record struct InterfaceMethodInfo(Type InstanceType, Type InterfaceType, MethodInfo Method);
private static MethodInfo GetInterfaceMethod(InterfaceMethodInfo info)
{
    var (instanceType, interfaceType, method) = info;
    var parameters = method.GetParameters();
    var genericArguments = method.GetGenericArguments();
    var interfaceMethods = instanceType
        .GetInterfaceMap(interfaceType)
        .InterfaceMethods
        .Where(m =&gt; IfMatch(method, genericArguments, parameters, m))
        .ToArray();
    var interfaceMethod = interfaceMethods.Length switch
    {
        0 =&gt; throw new MissingMethodException($"Can not find method {method.Name} in type {instanceType.Name}"),
        &gt; 1 =&gt; throw new AmbiguousMatchException($"Found more than one method {method.Name} in type {instanceType.Name}"),
        1 when interfaceMethods[0].IsAbstract =&gt; throw new InvalidOperationException($"The method {interfaceMethods[0].Name} is abstract"),
        _ =&gt; interfaceMethods[0]
    };
    if (method.IsGenericMethod)
        interfaceMethod = interfaceMethod.MakeGenericMethod(method.GetGenericArguments());
    return interfaceMethod;
}

第三步,用獲取到的接口方法,構(gòu)建DynamicMethod。其中的重點(diǎn)是使用OpCodes.Call,它的含義是以非虛方式調(diào)用一個(gè)方法,哪怕該方法是虛方法,也不去查找它的重寫,而是直接調(diào)用它自身。

private static DynamicMethod GetDynamicMethod(Type interfaceType, MethodInfo method, IEnumerable&lt;Type&gt; argumentTypes)
{
    var dynamicMethod = new DynamicMethod(
        name: "__IL_" + method.GetFullName(),
        returnType: method.ReturnType,
        parameterTypes: new[] { interfaceType, typeof(object[]) },
        owner: typeof(object),
        skipVisibility: true);
    var il = dynamicMethod.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    var i = 0;
    foreach (var argumentType in argumentTypes)
    {
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldc_I4, i);
        il.Emit(OpCodes.Ldelem, typeof(object));
        if (argumentType.IsValueType)
        {
            il.Emit(OpCodes.Unbox_Any, argumentType);
        }
        ++i;
    }
    il.Emit(OpCodes.Call, method);
    il.Emit(OpCodes.Ret);
    return dynamicMethod;
}

最后,將DynamicMethod轉(zhuǎn)為強(qiáng)類型的委托就完成了。考慮到性能的優(yōu)化,可以將最終的委托緩存起來,下次調(diào)用就不用再構(gòu)建一次了。

完整的代碼點(diǎn)這里 

方法2:利用函數(shù)指針

這個(gè)方法和方法1大同小異,區(qū)別是,在方法1的第二步,即找到接口方法的MethodInfo之后,獲取其函數(shù)指針,然后利用該指針構(gòu)造委托。這個(gè)方法其實(shí)是我最初找到的方法,方法1是其改進(jìn)。在此就不多做介紹了

方法3:利用Fody在編譯時(shí)對(duì)接口方法進(jìn)行IL的call調(diào)用

方法1雖然可行,但是肉眼可見的性能損失大,即使是用了緩存。于是乎我利用Fody編寫了一個(gè)插件InterfaceBaseInvoke.Fody。

其核心思想就是在編譯時(shí)找到目標(biāo)接口方法,然后使用call命令調(diào)用它就行了。這樣可以把性能損失降到最低。該插件的使用方法可以參考項(xiàng)目介紹。

性能測試

方法平均用時(shí)內(nèi)存分配
父類的base調(diào)用0.0000 ns-
方法1(DynamicMethod)691.3687 ns776 B
方法2(FunctionPointer)1,391.9345 ns1,168 B
方法3(InterfaceBaseInvoke.Fody0.0066 ns-

總結(jié)

本文探討了幾種實(shí)現(xiàn)接口的base調(diào)用的方法,其中性能以InterfaceBaseInvoke.Fody最佳,在C#官方支持以前推薦使用,更多關(guān)于C#實(shí)現(xiàn)接口base調(diào)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • c#中的interface abstract與virtual介紹

    c#中的interface abstract與virtual介紹

    abstract 與virtual : 方法重寫時(shí)都使用 override 關(guān)鍵字,interface中的方法和abstract方法都要求實(shí)現(xiàn)
    2013-07-07
  • C#中SerialPort的使用教程詳解

    C#中SerialPort的使用教程詳解

    SerilPort是串口進(jìn)行數(shù)據(jù)通信的一個(gè)控件,這篇文章主要為大家詳細(xì)介紹了C#中SerialPort的使用,具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-12-12
  • C# JsonHelper 操作輔助類,拿來直接用

    C# JsonHelper 操作輔助類,拿來直接用

    本文總結(jié)了一些常用的JSON操作輔助類,包括轉(zhuǎn)換、判斷、Ajax異步等操作,希望能幫到大家。
    2016-05-05
  • C#新手常犯的錯(cuò)誤匯總

    C#新手常犯的錯(cuò)誤匯總

    這篇文章主要介紹了C#新手常犯的錯(cuò)誤匯總,對(duì)于經(jīng)驗(yàn)豐富的C#程序員同樣具有很好的參考借鑒價(jià)值,需要的朋友可以參考下
    2014-08-08
  • Avalonia封裝實(shí)現(xiàn)指定組件允許拖動(dòng)的工具類

    Avalonia封裝實(shí)現(xiàn)指定組件允許拖動(dòng)的工具類

    這篇文章主要為大家詳細(xì)介紹了Avalonia如何封裝實(shí)現(xiàn)指定組件允許拖動(dòng)的工具類,文中的示例代碼講解詳細(xì),感興趣的小伙伴快跟隨小編一起來學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • C#單向鏈表實(shí)現(xiàn)非升序插入方法的實(shí)例詳解

    C#單向鏈表實(shí)現(xiàn)非升序插入方法的實(shí)例詳解

    單向鏈表是一種數(shù)據(jù)結(jié)構(gòu),其中元素以線性方式連接在一起,每個(gè)元素都指向下一個(gè)元素,非升序插入意味著元素不是按升序(從小到大)插入鏈表中,本文給大家介紹了C#單向鏈表實(shí)現(xiàn)非升序插入方法的實(shí)例,需要的朋友可以參考下
    2024-03-03
  • C#實(shí)現(xiàn)打開指定目錄和指定文件的示例代碼

    C#實(shí)現(xiàn)打開指定目錄和指定文件的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用C#實(shí)現(xiàn)打開指定目錄、打開指定目錄且選中指定文件、打開指定文件,感興趣的小伙伴可以嘗試一下
    2022-06-06
  • 詳解二維碼生成工廠

    詳解二維碼生成工廠

    本篇文章主要分享的是3個(gè)免費(fèi)的二維碼接口的對(duì)接代碼和測試得出的注意點(diǎn)及區(qū)別。具有很好的參考價(jià)值,需要的朋友一起來看下吧
    2016-12-12
  • C#異常處理詳解

    C#異常處理詳解

    這篇文章介紹了C#異常處理,有需要的朋友可以參考一下
    2013-10-10
  • 如何使用C#修改本地Windows系統(tǒng)時(shí)間

    如何使用C#修改本地Windows系統(tǒng)時(shí)間

    這篇文章主要介紹了如何使用C#修改本地Windows系統(tǒng)時(shí)間,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下
    2021-01-01

最新評(píng)論