【问题标题】:Why is the compiler choosing the wrong method overload?为什么编译器选择了错误的方法重载?
【发布时间】:2016-02-02 00:14:05
【问题描述】:

我有这个简单的方法:

public void CacheDelegate(Object obj, MemberInfo memberInfo)
{
   switch (memberInfo.MemberType)
   {
    case MemberTypes.Field:
       var fieldInfo = (FieldInfo) memberInfo;
       CacheDelegate(obj, fieldInfo);
       break;
    case MemberTypes.Property:
       var propertyInfo = (PropertyInfo) memberInfo;
       CacheDelegate(obj, propertyInfo);
       break;
    case MemberTypes.Method:
       var methodInfo = (MethodInfo) memberInfo;
       CacheDelegate(obj, methodInfo);
       break;
    default:
       throw new Exception("Cannot create a delegate for MemberInfo provided.");
    }
}

上面的方法解析了memberInfo的类型,并从下面调用了适用的方法:

public void CacheDelegate(Object obj, FieldInfo fieldInfo)
{
   // Do stuff...
}

public void CacheDelegate(Object obj, PropertyInfo propertyInfo)
{
   // Do stuff...
}

public sealed override void CacheDelegate(Object obj, MethodInfo methodInfo)
{
   // Do stuff...
}

问题是最后一个case标签,case MemberTypes.Method,没有调用带有Method Info重载的CacheDelegate方法,而是调用带有Member Info重载的CacheDelegate!所以它基本上只是一遍又一遍地递归地调用自己。我尝试在调用方法时指定参数名称methodInfo:methodInfo,但是Unity引擎告诉我最好的重载方法不包含名为methodInfo的参数。

我不知道为什么会发生这种情况。 任何帮助将不胜感激。

【问题讨论】:

  • 为什么最后一个方法被密封并覆盖了某些东西?
  • @DavidG 对于要密封的方法,基本方法需要是虚拟的或抽象的。要实现虚拟或抽象方法,需要重写它。删除 override 关键字将产生错误,“方法无法密封,因为它不是覆盖”。删除sealed 关键字将允许派生类型实现它们自己的方法版本。
  • @rafael-at-jags 我知道这意味着什么,我只是在暗示您没有给我们足够的上下文来正确回答。但你现在有了一个可行的答案。
  • @Rob 通过base.… 调用它会跳过虚拟调度并对基实现执行call 而不是callvirt,强制转换为bast 类型将获得基于基的重载挑选,但仍然是一个虚拟电话。
  • 你做错了吗?我无法通过类似的测试重现此行为。

标签: c# unity3d polymorphism


【解决方案1】:

重载解析的工作原理如下。

从调用的类型开始,找到在该类型上声明的可以使用的方法集。

如果该集合为空,则尝试对声明的基类型或接口进行相同操作。继续向上移动,直到找到至少一种匹配的方法,否则会出错。

在找到的集合中,使用最具体的方法。如果是平局则出错。

因此,在四个方法中,三个在此类中声明。这三个中两个不适用。这样就只剩下 public void CacheDelegate(Object obj, MemberInfo memberInfo) 显然是要调用的正确类,所以它被调用了。

您可以使用((BaseType)this).CacheDelegate(obj, methodInfo); 来强制调用您想要的调用,因为基本类型只有一个CacheDelegate 重载可供选择。

【讨论】:

  • 这对我来说是令人惊讶的行为,因为 override 仍然在类中声明 - 它应该是前 4 个考虑的方法的一部分。关于为什么决定不将覆盖方法视为在当前类中声明的任何想法?
  • 不能声明覆盖,如果还没有声明,怎么覆盖?无论如何,给定的行为具有合理的前向兼容行为;基类被赋予一个新方法并不能像这样轻易地完全破坏派生类的每次使用。
  • 这似乎是一个很好的解决方案。到目前为止,我所做的是创建另一个私有方法,所有方法都在内部调用。
  • 如果从头开始设计,我自己可能会采取相反的方法;再次为派生类中的方法指定与基类中的方法不同的名称。 (或者,如果我可以更改基类,请将基于switch 的调度移到它上面)。如果派生类的方法名称与基类中的方法名称相同,这可能会导致混淆,除非它们只添加全新的重载,即使您没有在此处遇到问题。
【解决方案2】:

Jon Hanna 已经解释了为什么会发生这种情况,我将通过提供源规范进行补充,您可以在其中阅读详细信息:https://msdn.microsoft.com/en-us/library/aa691336(v=vs.71).aspx

您可以通过以下几种方式解决您的问题:

  • 不要覆盖或重载该方法,使用不同的名称。
  • 不要覆盖那个方法,使用不同的参数重载,比如添加object ignoreMe。这将强制重载兼容,但大多数人会同意它远非优雅。
  • 使用new 隐藏方法,而不是覆盖。当涉及方法隐藏时,我不能 100% 确定重载解析是如何工作的,但它应该导致它使用正确的方法。请记住,这样做当然会消除它的多态性。
  • 使用反射手动找到正确的重载并调用它。这是最混乱的,也是开销最大的。这可能是也可能不是问题,具体取决于您的情况。但是,如果您真的想使用精确的覆盖/重载组合,它是唯一保留完整多态性的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-02
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多