【问题标题】:Why can't I declare C# methods virtual and static?为什么我不能将 C# 方法声明为虚拟和静态?
【发布时间】:2010-09-19 21:09:09
【问题描述】:

我有一个助手类,它只是一堆静态方法,并且想要子类化助手类。根据子类,某些行为是唯一的,因此我想从基类调用虚拟方法,但由于所有方法都是静态的,我无法创建普通的虚拟方法(需要对象引用才能访问虚拟方法)。

有没有办法解决这个问题?我想我可以使用单例。HelperClass.Instance.HelperMethod() 并不比 HelperClass.HelperMethod() 差多少。任何能指出某些支持虚拟静态方法的语言的人都可以加分。

编辑:好吧,我疯了。谷歌搜索结果让我觉得我不在那里。

【问题讨论】:

标签: c# oop


【解决方案1】:

可以通过在派生类中使用 new 关键字并在基类中抛出 NotSupportedException() 来模拟功能。

public class BaseClass{
    public static string GetString(){
        throw new NotSupportedException();   // This is not possible
    }
}

public class DerivedClassA : BaseClass {
    public static new string GetString(){
        return "This is derived class A";
    }
}

public class DerivedClassB : BaseClass {
    public static new string GetString(){
        return "This is derived class B";
    }
}

static public void Main(String[] args)
{
    Console.WriteLine(DerivedClassA.GetString());  // Prints "This is derived class A"
    Console.WriteLine(DerivedClassB.GetString());  // Prints "This is derived class B"
    Console.WriteLine(BaseClass.GetString());      // Throws NotSupportedException
}

由于无法在编译时检测到这种情况,并且 IntelliSense 不会建议在派生类中实现此类功能,这可能是一个令人头疼的问题。

还有一条评论建议使用 NotImplementedException()。 Microsoft 的文档表明不应处理这些异常,因此它们中的任何一个都应该起作用。

NotSupportedExceptionNotImplementedException 之间的区别在this blog 中有注释。

【讨论】:

    【解决方案2】:

    确实,这可以在 Delphi 中完成。一个例子:

    type
      TForm1 = class(TForm)
        procedure FormShow(Sender: TObject);
      end;
    
      TTestClass = class
      public
        class procedure TestMethod(); virtual;
      end;
    
      TTestDerivedClass = class(TTestClass)
      public
        class procedure TestMethod(); override;
      end;
    
      TTestMetaClass = class of TTestClass;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    class procedure TTestClass.TestMethod();
    begin
      Application.MessageBox('base', 'Message');
    end;
    
    class procedure TTestDerivedClass.TestMethod();
    begin
      Application.MessageBox('descendant', 'Message');
    end;
    
    
    procedure TForm1.FormShow(Sender: TObject);
    var
      sample: TTestMetaClass;
    begin
      sample := TTestClass;
      sample.TestMethod;
      sample := TTestDerivedClass;
      sample.TestMethod;
    end;
    

    很有趣。我不再使用 Delphi,但我记得能够使用元类功能在自定义设计器画布上非常轻松地创建不同类型的控件:控件类,例如。 TButton、TTextBox 等是一个参数,我可以使用实际的元类参数调用适当的构造函数。

    有点像穷人的工厂模式:)

    【讨论】:

    • c# 不支持静态虚拟方法这一事实让我抓狂……尤其是考虑到 c# 是由 10 年前设计 delphi 的同一个人 (Anders Hejlsberg) 设计的
    【解决方案3】:

    我不认为你疯了。您只想使用 .NET 目前不可能实现的功能。

    如果我们谈论泛型,您对虚拟静态方法的请求将非常有意义。 例如,我未来对 CLR 设计师的要求是允许我编写这样的接口:

    public interface ISumable<T>
    {
      static T Add(T left, T right);
    }
    

    并像这样使用它:

    public T Aggregate<T>(T left, T right) where T : ISumable<T>
    {
      return T.Add(left, right);
    }
    

    但现在是不可能的,所以我是这样做的:

        public static class Static<T> where T : new()
        {
          public static T Value = new T();
        }
    
        public interface ISumable<T>
        {
          T Add(T left, T right);
        }
    
        public T Aggregate<T>(T left, T right) where T : ISumable<T>, new()
        {
          return Static<T>.Value.Add(left, right);
        }
    

    【讨论】:

    • @SeeR: -1: 如果我错了我会更正,但这并不能解决原来的问题。
    • @John Saunders:他想要静态虚拟方法,但这是不可能的——他必须为此使用实例方法。他也不想在每次他想使用这个静态(现在是实例)方法时实例化这个类 - 这就是我创建 Static 类的原因。现在,对于整个应用程序,他将只有一个类的实例。我认为这种功能的税收是可以接受的。 Aggregate 方法只是他如何使用它的一个例子。所以总而言之,我们在 c# 中有静态虚拟替换 - 这不是有问题的请求吗?
    • @SeeR:如果所讨论的类型满足new 约束,则您的方法可以正常工作。静态虚拟方法将允许以类型安全的方式将此类构造用于不满足 new 约束的类型。或者,可以让Static 类没有new 约束,而是尝试使用反射来创建具有特殊参数化构造函数的对象。不幸的是,我不知道有什么方法可以使这种类型安全。
    【解决方案4】:

    有一种方法可以强制从抽象泛型类继承“抽象静态”方法。如下图:

    public abstract class Mother<T> where T : Mother<T>, new()
    {
        public abstract void DoSomething();
    
        public static void Do()
        {
            (new T()).DoSomething();
        }
    
    }
    
    public class ChildA : Mother<ChildA>
    {
        public override void DoSomething() { /* Your Code */ }
    }
    
    public class ChildB : Mother<ChildB>
    {
        public override void DoSomething() { /* Your Code */ }
    }
    

    示例(使用之前的母亲):

    public class ChildA : Mother<ChildA>
    {
        public override void DoSomething() { Console.WriteLine("42"); }
    }
    
    public class ChildB : Mother<ChildB>
    {
        public override void DoSomething() { Console.WriteLine("12"); }
    }
    
    public class Program
    {
        static void Main()
        {
            ChildA.Do();  //42
            ChildB.Do();  //12
            Console.ReadKey();
        }
    }
    

    这不是很好,因为您只能从一个抽象类继承,它会要求您对 new() 实现宽容。

    更多,我认为这将是昂贵的内存,具体取决于您继承的类的大小。 如果您有内存问题,您必须在新的公共方法中设置每个属性/变量,这是一种具有默认值的糟糕方法。

    【讨论】:

      【解决方案5】:

      静态方法存在于类的实例之外。它不能使用任何非静态数据。

      虚拟方法将被重载函数“覆盖”,具体取决于实例的类型

      所以你在静态和虚拟之间有明显的矛盾。

      这不是支持问题,这是一个概念。

      更新:我在这里被证明是错误的(见 cmets):

      所以我怀疑你会找到任何支持虚拟的 OOP 语言 静态方法。

      【讨论】:

      • 可以在实例类型上覆盖虚拟方法。所有子类都可以共享重载函数的相同实现。这就是为什么你想让它们既是静态的又是虚拟的。那是后期静态绑定,在 PHP5 中可用。
      • 不,没有矛盾,完全是编译器支持的问题,Python等很多语言都是这样做的。他们通过一流的课程来做到这一点。也就是说,类本身就是对象,可以分配给变量并调用它们的方法。 (搜索“classmethod”以了解更多信息。)
      • Object Pascal(Delphi 和开源的 Lazarus 等价物)一直支持它们
      【解决方案6】:

      覆盖方法提供了从基类继承的成员的新实现。被覆盖声明覆盖的方法称为被覆盖的基方法。被覆盖的基方法必须与覆盖方法具有相同的签名。 您不能覆盖非虚拟或静态方法。被覆盖的基方法必须是虚拟的、抽象的或覆盖的。

      覆盖声明不能改变虚方法的可访问性。覆盖方法和虚拟方法必须具有相同的访问级别修饰符。

      您不能使用 new、static 或 virtual 修饰符来修改覆盖方法。

      覆盖属性声明必须指定与继承属性完全相同的访问修饰符、类型和名称,并且被覆盖的属性必须是虚拟的、抽象的或覆盖的。

      【讨论】:

        【解决方案7】:

        你可以通过一个普通的静态方法来达到同样的效果,然后用new关键字隐藏它

        public class Base 
        {
            //Other stuff
        
            public static void DoSomething()
            {
                Console.WriteLine("Base");
            }
        }
        
        public class SomeClass : Base
        {
            public new static void DoSomething()
            {
                Console.WriteLine("SomeClass");
            }
        }
        public class SomeOtherClass : Base
        {
        }
        

        然后你可以像这样调用方法

        Base.DoSomething(); //Base
        SomeClass.DoSomething(); //SomeClass
        SomeOtherClass.DoSomething(); //Base
        

        【讨论】:

        • 这可能会产生意想不到的后果。例如,如果在您的基类中有另一个调用 DoSomething 的方法,并且您调用 SomeClass.ThatOtherMethod(),则该方法调用基类的 DoSomething,而不是 SomeClass 的版本。 dotnetfiddle.net/scRWKD
        • 在基类中抛出 NotImplementedException 以确保没有调用它
        【解决方案8】:

        你可以使用新的关键字

        namespace AspDotNetStorefront
        {
            // This Class is need to override StudioOnlineCommonHelper Methods in a branch
            public class StudioOnlineCommonHelper : StudioOnlineCore.StudioOnlineCommonHelper
            {
                //
                public static new void DoBusinessRulesChecks(Page page)
                {
                    StudioOnlineCore.StudioOnlineCommonHelper.DoBusinessRulesChecks(page);
                }
            }
        }
        

        【讨论】:

          【解决方案9】:

          Mart 使用了“新”关键字。 我实际上来到这里是因为我需要这种类型的功能,而 Mart 的解决方案运行良好。事实上,我把它做得更好,并将我的基类方法抽象为强制程序员提供这个字段。

          我的场景如下:

          我有一个基类 HouseDeed。每种房屋类型都源自 HouseDeed 必须有一个价格。

          这里是部分基础的 HouseDeed 类:

          public abstract class HouseDeed : Item
          {
              public static int m_price = 0;
              public abstract int Price { get; }
              /* more impl here */
          }
          

          现在让我们看看两种派生的房屋类型:

          public class FieldStoneHouseDeed : HouseDeed
          {
              public static new int m_price = 43800;
              public override int Price { get { return m_price; } }
              /* more impl here */
          }
          

          还有……

          public class SmallTowerDeed : HouseDeed
          {
              public static new int m_price = 88500;
              public override int Price { get { return m_price; } }
              /* more impl here */
          }
          

          如您所见,我可以通过类型 SmallTowerDeed.m_price 和实例 new SmallTowerDeed().Price 访问房屋价格 并且是抽象的,这种机制让程序员为每种新的衍生房屋类型提供价格。

          有人指出“静态虚拟”和“虚拟”在概念上是如何相互矛盾的。我不同意。在此示例中,静态方法不需要访问实例数据,因此满足以下要求:(1)仅通过 TYPE 可获得价格,以及(2)提供价格。

          【讨论】:

          • 这个答案似乎与问题无关。这也是一个有点糟糕的例子。 m_price 是一个实现细节,作为 HouseDeed 的一部分并没有增加任何内容。最好将其从 HouseDeed 中删除并在两个子类中将 m_price 设为私有。没有滥用“新”关键字的结果相同。
          • 元数据不相切。这是一个相关的需求。所有覆盖都是实现细节。你可以类似地争辩说,“for”没有给语言增加任何东西,因为它有“if”和“goto”。
          【解决方案10】:

          我来自 Delphi,这是我在 c# 中非常怀念的许多功能之一。 Delphi 将允许您创建类型化类型引用,并且您可以在需要父类类型的任何地方传递派生类的类型。这种将类型视为对象的处理具有强大的实用性。特别是允许元数据的运行时确定。我在这里可怕地混合了语法,但在 c# 中它看起来像:

              class Root {
                 public static virtual string TestMethod() {return "Root"; }
              }
              TRootClass = class of TRoot; // Here is the typed type declaration
          
              class Derived : Root {
                 public static overide string TestMethod(){ return "derived"; }
              }
          
             class Test {
                  public static string Run(){
                     TRootClass rc;
                     rc = Root;
                     Test(rc);
                     rc = Derived();
                     Test(rc);
                  }
                  public static Test(TRootClass AClass){
                     string str = AClass.TestMethod();
                     Console.WriteLine(str);
                  }
              } 
          

          会产生: 根 派生的

          【讨论】:

            【解决方案11】:

            实际上可以通过使用关键字new 而不是virtual 来为方法或成员组合虚拟和静态。

            这是一个例子:

            class Car
            {
                public static int TyreCount = 4;
                public virtual int GetTyreCount() { return TyreCount; }
            }
            class Tricar : Car
            {
                public static new int TyreCount = 3;
                public override int GetTyreCount() { return TyreCount; }
            }
            
            ...
            
            Car[] cc = new Car[] { new Tricar(), new Car() };
            int t0 = cc[0].GetTyreCount(); // t0 == 3
            int t1 = cc[1].GetTyreCount(); // t1 == 4
            

            显然TyreCount 值可以在覆盖的GetTyreCount 方法中设置,但这避免了重复值。可以从类和类实例中获取值。

            现在有人能找到该功能的真正智能用法吗?

            【讨论】:

            • @Mart:我差点给你投了反对票,直到我意识到你的例子很糟糕。代替静态字段,将示例更改为使用静态方法,其中派生类调用基类方法,然后执行更多操作。
            【解决方案12】:

            你没疯。您所指的称为后期静态绑定;它最近被添加到 PHP 中。有一个很棒的线程来描述它 - 这里:When would you need to use late static binding?

            【讨论】:

              【解决方案13】:

              因为虚方法使用实例化对象的定义类型来确定执行哪个实现,(与引用变量的声明类型相反)

              ...当然,静态就是根本不在乎是否有类的实例化实例...

              所以这些是不兼容的。

              底线是,如果您想根据实例是哪个子类来改变行为,那么方法应该是基类上的虚拟方法,而不是静态方法。

              但是,由于您已经拥有这些静态方法,现在需要覆盖它们,您可以通过以下方式解决您的问题: 将虚拟实例方法添加到简单地委托给静态方法的基类中,然后在每个派生子类中酌情覆盖那些虚拟实例包装方法(不是静态方法)...

              【讨论】:

                【解决方案14】:

                虚拟静态方法没有意义。如果我调用HelperClass.HelperMethod();,为什么我会期望调用一些随机子类的方法?当您拥有HelperClass 的 2 个子类时,该解决方案就真的崩溃了——您会使用哪一个?

                如果您想拥有可覆盖的静态类型方法,您可能应该使用:

                • 单例,如果您希望全局使用相同的子类。
                • 传统类层次结构,带有工厂或依赖注入,如果您希望在应用程序的不同部分有不同的行为。

                选择对您的情况更有意义的解决方案。

                【讨论】:

                • -1 因为你忘记了泛型。 T.Add(左T,右T);很有道理。不是吗?
                • 您将如何在子类中覆盖您的实现?
                • 是的,但是子类的实现在静态上下文中不能从基类型获得,这是原始问题提出的问题。
                • 是的,它们确实有意义,这只是语义问题。我认为如果在类中声明一个静态方法是有意义的,那么允许在每个类型的基础上覆盖它是有意义的。请参阅此线程中的 Delphi 示例。您可能会争辩说“静态”具有明确的含义;好吧,例如,在 C 中,它与 C# 和 Java 的含义非常不同。
                • @etrusco 是对的。其他语言,如 Python,具有虚拟静态方法。在那些语言中,对象可用于动态调度,但不能传递给方法(因为方法是静态的)。
                【解决方案15】:

                我听说 Delphi 支持这样的东西。似乎它是通过将类作为元类的对象实例来实现的。

                我没有看到它起作用,所以我不确定它是否起作用,或者这样做有什么意义。

                附:如果我错了,请纠正我,因为它不是我的域。

                【讨论】:

                  猜你喜欢
                  • 2013-08-01
                  • 2010-09-06
                  • 2011-01-23
                  • 2014-05-12
                  • 2010-09-24
                  • 2012-06-29
                  • 2011-10-13
                  相关资源
                  最近更新 更多