C# 为什么C不允许调用base.SomeAbstractMethod

C# 为什么C不允许调用base.SomeAbstractMethod,c#,oop,abstract-class,C#,Oop,Abstract Class,下面是讨论的一些代码 abstract class ClassA { public abstract void StartProcess(); } class ClassB : ClassA { public override void StartProcess() { Console.WriteLine("ClassB: Render"); } } class ClassC : ClassA {

下面是讨论的一些代码

  abstract class ClassA
  {
    public abstract void StartProcess();
  }

  class ClassB : ClassA
  {
    public override void StartProcess()
    {      
      Console.WriteLine("ClassB: Render");
    }
  }

  class ClassC : ClassA
  {
    public override void StartProcess()
    {
      base.StartProcess();//This is where the compiler complains
      Console.WriteLine("ClassC: Render");
    }
  }

在所有人都跳下我的喉咙之前,让我说,我完全知道为什么它并没有。但是,在某些情况下,这样做是有意义的,可以避免必须将基类的方法声明为虚拟的,但实现为空

来自Delphi的背景,我们可以在Delphi中实现这一点,并将其用于我们的课堂设计。如果错误地调用基类上的抽象方法(在运行时),则会出现“抽象错误”

然后,我希望(德尔福)编译器检查我之前! 现在我希望(C#)编译器能让我这么做! 这有多奇怪

问题是: 编译器/抖动不能简单地忽略这样的调用并发出警告而不是错误吗? 其他人看到/感受到这种痛苦吗

在我的案例中,我需要的是以下内容: ClassA是库的一部分(不控制该类) 生成ClassC(有点像ASP.NET页面的编译方式或Razor视图的编译方式)

但是库的用户可以定义一个ClassB,然后ClassC将从ClassB而不是ClassA(生成时)。与ASP.NET页面通常从System.Web.UI.Page下降的方式类似,但是如果您定义了自己的“基”页面和应用程序中的其他页面现在从基本页面派生,然后生成的类从基本页面派生(即从System.Web.UI.page派生)

我希望这一部分是清楚的。然后看看我介绍的代码,我无法让ClassC的实例调用到ClassB的实现中,因为代码生成器不知道包含base.StartProcess()

编辑 似乎有些人没有完全理解我所写的内容。因此,假设您正在编写代码生成部分,该部分生成从ClassA派生的ClassC。好吧,由于该方法是anstract(在ClassA中),您无法生成调用StartProcess()的代码行(因为编译器不允许)。因此,如果有人定义ClassB,代码生成仍然不会调用base.StartProcess()。这实际上是ASP.NET MVC视图中发生的情况

理想情况下,我希望编译器忽略它。它会忽略许多事情,例如对空引用调用dispose

我想进行一次讨论,而不是被传给

EDIT2 让我们假设我们有一个层次结构,如上面的代码所示,并且它是有效的。
我们现在的机会是,基类ClassA可能(将来)有一个StartProcess()子代调用的实现。今天唯一的方法是定义没有实体的虚拟方法。但这让我觉得有点讨厌。

调用
base.StartProcess()怎么可能有意义
何时声明为抽象?不可能有可调用的实现,因此编译器禁止它

就我个人而言,我喜欢在编译时看到错误,而不是在执行时看到错误,或者让抖动忽略我专门做的调用。如果它返回了一个你分配给变量的值呢?如果该方法不存在,该变量值应该是什么

如果ClassC将从ClassB派生,那么您就不会遇到问题,因为您不会调用抽象基方法。但是您的代码声明它直接从ClassA派生,而不是ClassB。如果生成ClassC,则应该生成它以从ClassB派生,这很好

我个人认为编译器在这里做的事情完全正确

编辑:我只是想明确我认为合适的解决方案是:

  • 如果您希望能够从任何派生类调用
    base.M()
    ,那么应该将其设置为具有无操作实现的虚拟方法,而不是抽象方法
  • 如果您有一个代码生成器,它只在生成基类实现了
    M
    的类的情况下才应该生成对
    base.M()的调用,那么就由代码生成器来实现这一点-这种语言不应该让其他人受苦(通过将错误报告延迟到执行时间,或者更糟糕的是,通过简单地执行no-op来吞咽错误)仅仅是因为一个工具编写错误
我认为,调用抽象基方法或使其成为执行时错误的缺点比问题中描述的问题更糟糕


现在,一个有趣的语言特性可能在这里很有用,这就是虚拟方法的思想,它强制重写在重写之前或之后调用基类实现……类似于派生类中的构造函数总是必须直接或通过另一个方法调用基类中的构造函数我强烈怀疑这种特性的复杂性(返回值会发生什么?如何使用SpecifyBefore/after语义?异常情况如何?)在简单的类层次结构中,可以以更简单的方式执行相同的任务。

您从
ClassA
派生
ClassC
,您希望base.StartProcess实际做什么


你真的想从
ClassB
派生吗?我认为让编译器编译这样的代码是没有意义的

另一方面,我理解您所处的情况。应该对代码生成器进行修复:它不应该生成对抽象方法的调用(可以使用反射进行检查)。如果您无法访问代码生成器的代码,恐怕您没有太多选项

您可以创建一个facade对象,该对象派生自a,但将所有抽象方法实现为空的虚拟方法,并操纵代码生成器以使用它而不是a
base.StartProcess();
this.BaseCall("StartProcess");
public static class BaseExtensions {
  public static void BaseCall(this object self, string methodName, params object[] parameters) {
    self.BaseCall(methodName, typeof(void), null, parameters);
  }
  public static T BaseCallWithReturn<T>(this object self, string methodName, T defaultReturn = default(T), params object[] parameters) {
    return (T)self.BaseCall(methodName, typeof(T), defaultReturn, parameters);
  }
  private static object BaseCall(this object self, string methodName, Type returnType, object defaultReturn, object[] parameters) {
    var parameterTypes = parameters.Select(p => p.GetType()).ToArray();
    if (self.GetType().BaseType == null) return null;
    var method = self.GetType().BaseType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null);
    if (method == null || method.IsAbstract) return defaultReturn;
    var dm = new DynamicMethod(methodName, returnType, new Type[] { self.GetType() }.Concat(parameterTypes).ToArray(), self.GetType());
    var il = dm.GetILGenerator();
    PushParameters(il, parameterTypes.Length);
    il.Emit(OpCodes.Call, method);
    il.Emit(OpCodes.Ret);
    return dm.Invoke(null, new object[] { self }.Concat(parameters).ToArray());
  }
  private static void PushParameters(ILGenerator il, int n) {
    il.Emit(OpCodes.Ldarg_0);
    for (int i = 0; i < n; ++i) {
      switch (i+1) {
        case 1: il.Emit(OpCodes.Ldarg_1); break;
        case 2: il.Emit(OpCodes.Ldarg_2); break;
        case 3: il.Emit(OpCodes.Ldarg_3); break;
        default: il.Emit(OpCodes.Ldarg_S, i+1); break;
      }
    }
  }
}