【问题标题】:C# derived classes, overload resolutionC# 派生类,重载决议
【发布时间】:2026-02-12 21:30:02
【问题描述】:

好的,我有一些从基类派生的不同对象,我已经将它们中的一堆放在了一个列表中。我想遍历列表并将每个推送到一个方法。我对每个人的类型签名都有单独的方法,但是编译器在抱怨。有人可以解释为什么吗?这是使用泛型的机会吗?如果是,如何使用?

class Base { }
class Level1 : Base { }
class Level2 : Level1 { }

...

List<Base> oList = new List<Base>();
oList.Add(new Level1());
oList.Add(new Level2());

...

...
foreach(Base o in oList)
{
   DoMethod(o);
}

...

void DoMethod(Level1 item) { }
void DoMethod(Level2 item) { }

我做错了什么?

【问题讨论】:

  • 我不确定它是否清楚,如果DoMethod 实际上是 Base、Level1、Level2 上的方法,我认为存在误解?你能澄清一下吗?
  • @Will P 对,对不起;我在外部处理对象,所以,不,DoMethod(s) 不是这些类的成员。

标签: c# overloading derived-class


【解决方案1】:

重载在编译时解决 - 你没有DoMethod(Base item) 方法 - 所以它不能解决调用。抛开列表和循环,你实际上是在写:

Base o = GetBaseFromSomewhere();
DoMethod(o);

编译器必须找到一个名为DoMethod 的方法,该方法适用于Base 类型的单个参数。没有这样的方法,所以失败了。

这里有几个选项:

  • 正如 Markos 所说,您可以使用 C# 4 中的动态类型来使 C# 编译器在 执行 时使用 o 引用的 实际 类型的对象应用重载到。
  • 您可以使用Visitor Pattern 有效地获得双重调度(我从来不喜欢这个)
  • 您可以使用asis

    Level1 x = o as Level2;
    if (x != null)
    {
        DoMethod(x); // Resolves to DoMethod(Level1)
    } 
    else
    {
        Level2 y = o as Level2;
        if (y != null)
        {
            DoMethod(y); // Resolves to DoMethod(Level2)
        }
    }
    

    再一次,这太丑了

  • 如果可能的话,重新设计你正在做的事情,以便能够使用正常的继承

【讨论】:

    【解决方案2】:

    重载方法使用变量的静态类型而不是运行时类型。

    你想使用继承和覆盖

    class Base { public virtual void DoMethod() { /* ... */  } }
    class Level1 : Base { public override void DoMethod() { /* ... */ } }
    class Level2 : Level1 { public override void DoMethod() { /* ... */ } }
    

    【讨论】:

      【解决方案3】:

      调用哪个方法是在编译时而不是运行时确定的,因此编译器无法知道调用哪个方法。您有 2 个选项: 切换对象的类型并调用适当的方法,或 如果您使用的是 .NET 4,请使用动态类型。

      foreach(dynamic o in oList)
      {
         DoMethod(o);
      }
      

      【讨论】:

      • 使用dynamic 会对性能产生一些影响。更重要的是,我认为这是一个适合老式多态性的地方。
      • @Steven:我们不确定。将DoMethod的逻辑放在Level1Level2中可能是完全不合适的。
      • @Jon:当然,我考虑的一种可能性是这可能需要某种形式的双重调度。尽管如此,我们应该能够在没有switch 声明的情况下完成这项工作。
      【解决方案4】:

      您没有 DoMethod(Base item) 方法。重载不是多态的。这通常通过使用虚方法来完成:

      class Base {
          public virtual void DoMethod() {...}
      }
      class Level1 : Base {
          public override void DoMethod() {...}
      }
      // etc..
      
      foreach(Base o in oList)
      {
          o.DoMethod();
      }
      

      【讨论】:

        【解决方案5】:

        在您的 foreach 循环中,o 具有 Base 类型,并且 DoMethod 重载均不采用 Base 实例。如果可能,您应该将 DoMethod 移动到 Base 并在两个子类中覆盖它:

        public class Base
        {
            public virtual void DoMethod() { ... }
        }
        

        【讨论】:

          【解决方案6】:

          为了扩展 Mark 的答案,DoMethod 应该是 Base 中的一个虚拟方法,您可以在列表中的每个项目上调用它。

          【讨论】:

          • Hans 和 Mark 在这点上领先我几分钟,所以归功于他们。我还给他们投了赞成票。
          【解决方案7】:

          我不知道所有细节,但如果确实不适合继承,您可以使用接口。

          声明接口,在每个类上实现它,然后你就可以直接转换到接口并从那里运行函数。我的 C# 有点不稳定,但类似于,

          Interface IMethodizable
          {
             void DoMethod();
          }
          
          class Level1 : IMethodizable {
            void DoMethod(){
              //insert code here
            }
          }
          
          class Level2 : IMethodizable {
            void DoMethod(){
              //insert code here
            }
          }
          

          如果类唯一的共同点就是该方法,则此方法特别有效。这与在基类中有一个虚拟化方法并覆盖它非常相似。所以这种模式只有在你不应该继承的情况下才会更好,否则 DoMethod 也必须在其他不从 base 继承的对象上运行,等等。

          【讨论】:

          • 这也适用于基类,但对于 OP 可能不合适(有关此问题的讨论,请参见上面的答案)。我实际上有一个类似的问题,在我的情况下它是不合适的——我的方法将取决于调用类,所以这就是实现的地方。我可能不得不使用一个大的 switch 语句,但听起来我应该阅读动态的,因为我很幸运能够针对 C# 4 进行编码。(即,对我来说,这是谷歌查找信息问题和讨论的一个很好的例子*!)
          【解决方案8】:

          由于 C# 7.0 模式匹配是另一种选择。

          有关详细信息,请参阅MSDN。 您的代码会喜欢:

          switch(o)
          {
              case Level2 level2: Do(level2); break;
              case Level1 level1: Do(level1); break;
              case Base @base: Do(@base); break;
              default: ...
              case null: ...
          }
          

          【讨论】:

          • 感谢 Patrik,您的榜样!
          • 当我有一组基类引用并且需要调用每个依赖于真实对象类型的方法时,它可以帮助我。它可以使用虚拟方法轻松完成,但每个实例都在对象模型中使用,并且必须仅包含数据。恕我直言,Switch...case 看起来比 if...else 带有“is”和“as”关键字好一点。