当前位置:网站首页>[raise bar C #] how to call the base of the interface

[raise bar C #] how to call the base of the interface

2022-06-10 22:51:00 u012804784

High quality resource sharing

Learning route guidance ( Click unlock ) Knowledge orientation Crowd positioning
🧡 Python Actual wechat ordering applet 🧡 Progressive class This course is python flask+ Perfect combination of wechat applet , From the deployment of Tencent to the launch of the project , Create a full stack ordering system .
Python Quantitative trading practice beginner Take you hand in hand to create an easy to expand 、 More secure 、 More efficient quantitative trading system

background

Released three years ago C#8.0 There is an important improvement in... Called Interface default implementation , From then on , The method defined in the interface can contain the method body , Default implementation .

But for the default implementation of the interface , Its implementation class or sub interface is in When rewriting this method It cannot be base call , Just like subclass overriding methods can be base.Method() like that . for example :

public interface IService
{
    void Proccess()
    {
        Console.WriteLine("Proccessing");
    }
}

public class Service : IService
{
    public void Proccess()
    {
        Console.WriteLine("Before Proccess");
        base(IService).Proccess(); //  Currently not supported , It is also the part that this article needs to discuss 
        Console.WriteLine("End Proccess");
    }
}

The original C# The team listed this feature as a plan for the next step ( Click here to view details ), However, three years later, it has not been put on the agenda . The lack of this feature is undoubtedly a great limitation , Sometimes we do need interfaces base Call to implement certain requirements . This article will introduce two ways to implement it .

Method 1: Use reflection to find the interface implementation and call it

The core idea of this method is , Use reflection to find the interface implementation you need to call MethodInfo, And then build DynamicMethod Use OpCodes.Call Just call it .

First, we define the method signature used to represent the interface method base call .

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

So the example in the previous section can be rewritten as :

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

So next , We need the basis lambda Expression to find its corresponding interface implementation , And then call it .

The first step is based on lambda Expression acquisition MethodInfo And parameters . It should be noted that , We also need to support the calling of attributes , In fact, attributes are also a method , So it can be handled together .

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

The second step , utilize Type.GetInterfaceMap Get the interface implementation method to be called . The key points to note here are ,instanceType.GetInterfaceMap(interfaceType).InterfaceMethods All methods of the interface will be returned , So you can't just match by method name , Because there may be various overloads 、 The generic parameter 、 also new Keyword to declare a method with the same name , So you can follow the Method name + Declaration type + Method parameter + Method generic parameters The only way to determine ( In the following code block IfMatch The implementation of the )

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 => IfMatch(method, genericArguments, parameters, m))
        .ToArray();

    var interfaceMethod = interfaceMethods.Length switch
    {
        0 => throw new MissingMethodException($"Can not find method {method.Name} in type {instanceType.Name}"),
        > 1 => throw new AmbiguousMatchException($"Found more than one method {method.Name} in type {instanceType.Name}"),
        1 when interfaceMethods[0].IsAbstract => throw new InvalidOperationException($"The method {interfaceMethods[0].Name} is abstract"),
        _ => interfaceMethods[0]
    };

    if (method.IsGenericMethod)
        interfaceMethod = interfaceMethod.MakeGenericMethod(method.GetGenericArguments());

    return interfaceMethod;
}

The third step , Use the obtained interface method , structure DynamicMethod. The emphasis is on using OpCodes.Call, It means to call a method in a non - virtual way , Even if the method is virtual , And don't look for a rewrite of it , Instead, it calls itself directly .

private static DynamicMethod GetDynamicMethod(Type interfaceType, MethodInfo method, IEnumerable 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;
}

Last , take DynamicMethod The conversion to strongly typed delegates is complete . Considering the optimization of performance , The final delegate can be cached , You don't have to build it again next time .

The complete code points here

Method 2: Using function pointers

This method and method 1 Be the same in essentials while differing in minor points , The difference is that , In the method 1 The second step , That is, to find the interface method MethodInfo after , Get its function pointer , Then use this pointer to construct a delegate . This method is actually the method I first found , Method 1 Is its improvement . There is no more introduction here

Method 3: utilize Fody Interface methods are modified at compile time IL Of call call

Method 1 It's possible , But the visible performance loss is large , Even with caching . So I used Fody Write a plug-in InterfaceBaseInvoke.Fody.

The core idea is to find the target interface method at compile time , And then use call Just call it with the command . This minimizes performance loss . For the usage of this plug-in, please refer to Project introduction .

Performance testing

| Method | The average time | Memory allocation |
| Of the parent class base call | 0.0000 ns | - |
| Method 1(DynamicMethod) | 691.3687 ns | 776 B |
| Method 2(FunctionPointer) | 1,391.9345 ns | 1,168 B |
| Method 3(InterfaceBaseInvoke.Fody) | 0.0066 ns | - |

summary

This paper discusses several methods to realize the interface base Method called , Among them, the performance is InterfaceBaseInvoke.Fody The best , stay C# The official support has previously recommended . Welcome to use , Be careful , Thank you. .

原网站

版权声明
本文为[u012804784]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/161/202206101629506176.html