C# 8 基本接口的默认方法调用解决方法

C# 8 base interface's default method invocation workaround

根据https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods 可以使用以下语法显式调用接口基实现。

base(IInterfaceType).Method();

但这似乎还没有实现。

是否有解决方法(例如反射)来实现此目的?


Example code to illustrate the problem

interface IA
{
    void M()
    {
        Console.WriteLine("IA.M");
    }
}

interface IB : IA
{
    void IA.M()
    {
        Console.WriteLine("IB.M");
    }
}

interface IC : IA
{
    void IA.M()
    {
        Console.WriteLine("IC.M");
    }
}

class D : IA, IB, IC
{
    public void M()
    {
        // base(IB).M(); Is not yet supported apparently
        ((IB)this).M(); // Throws stack overflow
    }
}

class Program
{
    static void Main(string[] args)
    {
        D d = new D();
        d.M();
    }
}

问题中的 link 指向复制的提案版本 from the proposal document in Github

特征是 cut in April 2019

Conclusion

Cut base() syntax for C# 8. We intend to bring this back in the next major release.

设计会议文档解释说,如果没有运行时支持(无法及时提供),该实现最多只能用于 C#,但不能用于 VB.NET。

If B.M is not present at run time, A.M() will be called. For base() and interfaces, this is not supported by the runtime, so the call will throw an exception instead. We'd like to add support for this in the runtime, but it is too expensive to make this release.

We have some workarounds, but they do not have the behavior we want, and are not the preferred codegen. Our implementation for C# is somewhat workable, although not exactly what we would like, but the VB implementation would be much more difficult. Moreover, the implementation for VB would require the interface implementation methods to be public API surface.

至于无限递归,这个

public void M()
{
    ((IB)this).M(); // Throws stack overflow
}

基本上就是

public void M()
{
    M(); // Throws stack overflow
}

默认接口成员的调用方式与通过接口显式实现的接口方法相同。此外,您要求在 this 上调用该方法,而不是 base.

有一个解决方法。 我让它工作了,使用 GetFunctionPointer

Warning do not use this code

static class BaseInterfaceInvocationExtension
{
    private static readonly string invalidExpressionMessage = "Invalid expression.";

    public static void Base<TInterface>(this TInterface owner, Expression<Action<TInterface>> selector)
    {
        if (selector.Body is MethodCallExpression methodCallExpression)
        {
            MethodInfo methodInfo = methodCallExpression.Method;
            string name = methodInfo.DeclaringType.FullName + "." + methodInfo.Name;
            Type type = owner.GetType();
            InterfaceMapping interfaceMapping = type.GetInterfaceMap(typeof(TInterface));
            var map = interfaceMapping;
            var interfaceMethod = map.InterfaceMethods.First(info =>
                info.Name == name);
            var functionPointer = interfaceMethod.MethodHandle.GetFunctionPointer();

            var x = methodCallExpression.Arguments.Select(expression =>
            {
                if (expression is ConstantExpression constantExpression)
                {
                    return constantExpression.Value;
                }
                var lambda = Expression.Lambda(Expression.Convert(expression, expression.Type));
                return lambda.Compile().DynamicInvoke();
            }).ToArray();
            Type actionType = null;
            if (x.Length == 0)
            {
                actionType = typeof(Action);
            }else if (x.Length == 1)
            {
                actionType = typeof(Action<>);
            }
            else if (x.Length == 2)
            {
                actionType = typeof(Action<,>);
            }
            var genericType = actionType.MakeGenericType(methodInfo.GetParameters().Select(t => t.ParameterType).ToArray());
            var instance = Activator.CreateInstance(genericType, owner, functionPointer);
            instance.GetType().GetMethod("Invoke").Invoke(instance, x);
        }
        else
        {
            throw new Exception(invalidExpressionMessage);
        }
    }
}

class D : IA, IB, IC
{
    public void M(int test)
    {
       this.Base<IB>(d => d.M(test));
    }
}

class Program
{
    static void Main(string[] args)
    {
        D d = new D();
        d.M(12);
        Console.ReadKey();
    }
}

这是一种解决方法。这并不理想。也许它会对某人有所帮助。

class C : IB
{
    public void IBM() => (this as IB).M();
}

class D : IA, IB, IC
{
    private C _c = new C();

    public void M()
    {
        _c.IBM();
    }
}

或者,这可能有效:

...
interface IB : IA
{
    void IA.M()
    {
        IB_M();
    }
  
    void IB_M()
    {
        Console.WriteLine("IB.M");
    }
}
...

class D : IA, IB, IC
{
    public void M()
    {
        (this as IB).IB_M();
    }
}

也不理想,但不那么冗长。