【问题标题】:C# 8 base interface's default method invocation workaroundC# 8 基本接口的默认方法调用解决方法
【发布时间】:2020-04-11 08:39:06
【问题描述】:

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

base(IInterfaceType).Method();

但这似乎还没有实现。

是否有解决方法(例如反射)来实现这一点?


说明问题的示例代码

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

【问题讨论】:

  • 您链接到一个提案文档。在 C# 8 中,不能调用基本方法句点。 ` ((IB)this).M();` 是显式调用语法,用于调用方法的实际实现,因此预计会出现堆栈溢出。本质上,这是一个M(){ M();}
  • 这不是 C# 8 中“尚未实现”的问题。因为这个版本是最终版本。基础调用可能出现在以后的版本中。
  • 不,没有解决方法(不依赖编译器内部,也就是说,我不会认为这是一个有效的解决方法)。这显然是需要的,它最初计划成为 C# 8 的一部分,但事实证明它过于雄心勃勃。该提案计划在 C# 9 中实现,但可能会发生变化。在那之前,除非使方法在其他名称/不同范围内可用,否则无法做到这一点。
  • 您为什么还要寻找基本调用?可能还有其他方法可以做任何你想做的事情

标签: c# reflection .net-core c#-8.0


【解决方案1】:

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

功能是cut in April 2019

结论

删减 C# 8 的 base() 语法。我们打算在下一个主要版本中恢复此功能。

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

如果 B.M 在运行时不存在,则将调用 A.M()。对于 base() 和接口,运行时不支持此操作,因此调用将引发异常。我们希望在运行时添加对此的支持,但发布此版本的成本太高。

我们有一些解决方法,但它们没有我们想要的行为,也不是首选的代码生成器。我们的 C# 实现有点可行,虽然不完全是我们想要的,但 VB 实现会困难得多。此外,VB的实现需要接口实现方法是公共API表面。

至于无限递归,这个

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

本质上是这样的

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

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

【讨论】:

  • 我使用 MethodInfo.GetFunctionPointer();但结果有点乱
【解决方案2】:

有一个解决方法。 我得到它的工作,使用GetFunctionPointer

警告不要使用此代码

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-04
      • 1970-01-01
      • 2019-04-20
      • 1970-01-01
      • 2018-06-13
      相关资源
      最近更新 更多