【问题标题】:How to force call a C# derived method如何强制调用 C# 派生方法
【发布时间】:2010-11-19 12:06:42
【问题描述】:

我有一个由某些工具生成的类,因此我无法更改它。生成的类很简单(没有接口,没有虚方法):

class GeneratedFoo
{
  public void Write(string p) { /* do something */ }
}

在 C# 项目中,我们希望提供一种方法,以便我们可以插入不同的 MyFoo 实现。所以我正在考虑让 MyFoo 派生自 GeneratedFoo

class MyFoo : GeneratedFoo
{
  public new void Write(string p) { /* do different things */ }
}

然后我有一个 CreateFoo 方法,它将返回 GeneratedFoo 或 MyFoo 类的实例。但是它总是调用 GeneratedFoo 中的方法。

GeneratedFoo foo = CreateFoo(); // if this returns MyFoo,
foo.Write("1"); // it stills calls GeneratedFoo.Write

这是预期的,因为它不是虚拟方法。但我想知道是否有办法(可能是 hack)让它调用派生方法。

谢谢,
伊恩

【问题讨论】:

  • "公共新无效" ?那是我以前从未见过的“新”的用法。这还有效吗?
  • 'new' 在这里表示,您已经从基类中明确地“覆盖”了方法,而没有覆盖它(因为它不是虚拟的) - 没有它,您实际上会收到编译器的警告。
  • 更具体地说 - 当您在派生类中为您的方法提供与基类中相同的名称,并且它不是覆盖方法时,编译器会给您一个警告,您的方法隐藏了继承的成员(因为您可能不想这样做),但如果你确定,你可以使用“new”关键字告诉编译器。
  • 当关键字被重载以根据上下文具有多种含义时,我总是很兴奋。谢谢你让我直截了当。

标签: c# class inheritance


【解决方案1】:

编写一个可安全转换为派生类型的扩展方法,并改为针对 that 引用调用该方法。

public static class Extensions
{
  public static void WriteFoo(this GeneratedFoo foo, string p)
  {
     MyFoo derived = foo as MyFoo;
     if (derived != null)
     {
        derived.Write(p);
     }
     else
     {
        foo.Write(p);
     }
  }
}

然后调用它

GeneratedFoo unknownFoo;
unknownFoo.WriteFoo("win");

注意:这是一个肮脏的黑客。一个更清晰的解决方案是首先问自己为什么你需要使用new 修饰符。重载Write(p) 的含义会使您的代码对维护者感到困惑。您可以轻松地声明它public void WriteToTarget() 或更具体的内容,以避免首先出现这种混淆。

【讨论】:

    【解决方案2】:

    添加到您的构建系统中的自定义 FX cop 规则怎么样?

    【讨论】:

      【解决方案3】:

      在方法上使用“new”不是覆盖,它隐藏了原始方法。这与多态性完全不同,应该避免,它的唯一目的是用于执行相同功能的方法的协变/逆变(例如,public Foo Clone(); 和 public new Bar Clone();)。另一个目的是用于遗留代码,当将同名方法添加到您无法控制且目前无法更改方法名称的基类时。

      虽然这两个方法同名,但它们是完全独立的方法,但它在类的方法表中占据不同的位置,其中覆盖替换了类的方法表中的现有方法。

      对此我的解决方案是使用 IFoo 之类的接口,然后编辑生成的类或使用代理类将其所有方法调用委托给 GeneratedFoo 的实例来实现 IFoo。

      
      public DelegatingFoo : IFoo
      {
        private GeneratedFoo foo;
      
        public DelegatingFoo(GeneratedFoo foo) { this.foo = foo; }
        public void Write(string p) { foo.Write(p); }
      }
      

      【讨论】:

        【解决方案4】:

        亚当给了你一个答案(正确的)。现在是你要求的破解的时候了:)

        
        class BaseFoo
        {
            public void Write() { Console.WriteLine("Hello simple world"); }
        }
        
        class DerFoo : BaseFoo
        {
            public void Write() { Console.WriteLine("Hello complicated world"); }
        }
        
        public static void Main()
        {
            BaseFoo bs = new DerFoo();
            bs.Write();
        
            bs.GetType().GetMethod("Write").Invoke(bs, null);
        }
        

        打印出来:

        你好简单的世界 你好复杂的世界

        【讨论】:

        • 那里不需要不安全
        • 是的,这是不安全的,因为当我需要编写一些非常奇怪的代码时,我在 VS 中打开的“测试”项目很少,所以 Main 默认情况下是不安全的,而且我也有很多奇怪的用法嘿嘿。
        • 无论如何,为了清楚起见,删除了不安全的内容(因为它已经过时了),感谢您发表评论。
        • 为此使用反射很慢,而且完全没有必要。另外,最好尽可能避免依赖魔术字符串。我建议使用 Tullo 的扩展方法(在此页面上)来获得相同的结果。
        • @MichaelGraczyk 当然,您对速度的担忧是对的。但是,我的解决方案与 Tullo 的不同,因为它总是调用“正确”的方法。 “正确”是底层的类型实现,即使你有继承链(所以你必须检查链中的每个基本类型并找到最基本的类型)。此外,如果您需要速度,因为您将经常调用此方法,您可以在获得有效方法后从 GetMethod("") 创建委托。不过,我同意这是一个 hack,主要是为了表明你有可能做这样的事情。
        【解决方案5】:

        您可以使用的一种方法是实际定义几个新类,这些类都派生自 GeneratedFoo;例如:

        public class MyFooBase : GeneratedFoo
        {
            public virtual void MyMethod() { ... }
        }
        
        public class MyFoo1 : MyFooBase { ... }
        
        public class MyFoo2 : MyFooBase { ... }
        

        这将允许您在生成的类中添加虚拟方法。

        【讨论】:

        • 但不要从生成的 foo 调用非虚拟方法,因为它是虚拟的。
        • 但是您可以编写一个新方法,该方法是虚拟的,并在派生类中调用您希望具有该行为的非虚拟方法,并且不调用派生类中不应具有该行为的非虚拟方法。
        【解决方案6】:

        如果生成的类没有虚方法,那么这是不可能的。如果您可以修改工具以将 Write 方法生成为虚拟方法,那么在 MyFoo 中使用关键字 override 而不是 new 来限定 Write 方法。 第二种选择是编写一个脚本/宏,在工具运行后更改 GeneratedFoo 的源代码,以便在构建过程中在所需方法之前注入字符串“virtual”。

        【讨论】:

          【解决方案7】:

          无法使方法虚拟化,不。非虚拟方法在编译时是静态链接的,无法更改。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-05-12
            • 1970-01-01
            • 2017-11-29
            • 1970-01-01
            • 1970-01-01
            • 2010-12-26
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多