【问题标题】:Why can't I use virtual/override on class variables as I can on methods?为什么我不能像在方法上那样对类变量使用虚拟/覆盖?
【发布时间】:2011-01-23 14:48:56
【问题描述】:

在以下示例中,我可以在 inherited 类中创建 virtual 方法 Show(),然后在 override strong>继承类。

我想用受保护的类变量 prefix同样的事情,但我得到了错误:

修饰符“虚拟”无效 对于这个项目

但由于我无法在我的类中将此变量定义为 virtual/override,因此我得到了编译器 warning

TestOverride234355.SecondaryTransaction.prefix' 隐藏继承的成员 'TestOverride234355.Transaction.prefix'。 如果 hidden 是,则使用 new 关键字 有意的。

幸运的是,当我添加 new 关键字时,一切工作正常,这没关系,因为我获得了相同的功能,但这提出了两个问题

  1. 为什么我可以对方法使用虚拟/覆盖,但不能对受保护的类变量使用?

  2. 虚拟/覆盖方法和 hide-it-with-new 方法之间实际上有什么区别,因为至少在这个示例中它们提供了相同的功能?

代码:

using System;

namespace TestOverride234355
{
    public class Program
    {
        static void Main(string[] args)
        {
            Transaction st1 = new Transaction { Name = "name1", State = "state1" };
            SecondaryTransaction st2 = 
                new SecondaryTransaction { Name = "name1", State = "state1" };

            Console.WriteLine(st1.Show());
            Console.WriteLine(st2.Show());

            Console.ReadLine();
        }
    }

    public class Transaction
    {
        public string Name { get; set; }
        public string State { get; set; }

        protected string prefix = "Primary";

        public virtual string Show()
        {
            return String.Format("{0}: {1}, {2}", prefix, Name, State);
        }
    }

    public class SecondaryTransaction : Transaction
    {
        protected new string prefix = "Secondary";

        public override string Show()
        {
            return String.Format("{0}: {1}, {2}", prefix, Name, State);
        }
    }
}

【问题讨论】:

  • 您的问题涉及变量,但标题涉及属性。这些是非常不同的事情。我建议您编辑问题或(更有可能)标题。

标签: c# oop virtual overriding new-operator


【解决方案1】:

覆盖一个字段并没有真正的意义。它是基类状态的一部分,如果继承类希望更改它,它应该可以在继承类中通过赋予它适当的可见性来更改。

您可以做的一件事是在继承类的构造函数中设置prefix

// Base class field declaration and constructor
protected string prefix;

public Transaction()
{
  prefix = "Primary";
}

// Child class constructor
public SecondaryTransaction()
{
  prefix = "Secondary";
}

您还可以创建属性而不是字段,并将属性设为虚拟。这将使您能够更改继承类中属性的 getter 和 setter 的行为:

// Base class
public virtual string Prefix { get { /* ... */ } set { /* ... */ } }

// Child class
public override string Prefix { get { /* ... */ } set { /* ... */ } }

编辑:关于在继承类设置变量之前在基构造函数中使用变量的问题,解决此问题的一种方法是在基类中定义初始化方法,覆盖它在继承类中,并在访问任何字段之前从基构造函数中调用它:

// Base class
public class Base
{
  protected string prefix;

  public Base()
  {
    Initialize();
    Console.WriteLine(prefix);
  }  

  protected virtual void Initialize()
  {
    prefix = "Primary";
  }
}

// Inheriting class
public class Child : Base
{
  public override void Initialize()
  {
    prefix = "Secondary";
  }
}

编辑 2:您还询问了 virtual/override 和名称隐藏(方法上的新关键字)之间的区别是什么,是否应该避免,以及它是否有用。 p>

名称隐藏是在隐藏虚拟方法的情况下打破继承的功能。即,如果您将Initialize() 方法隐藏在子类中,则基类将看不到它,也不会调用它。此外,如果 Initialize() 方法是公共的,则在基类型的引用上调用 Initialize() 的外部代码将在基类型上调用 Initialize()

当基类中的方法是非虚拟的,并且子类想要提供自己的不同实现时,名称隐藏很有用。但是请注意,这与 virtual/override 相同。基类型的引用调用基类型实现,子类型的引用调用子类型实现。

【讨论】:

  • 对,但是在我的实际代码中,我从继承类的构造函数(例如 public SecondaryTransaction() : base() )调用继承类的基本构造函数,并且基本构造函数使用这个变量,即那时还没有设置。
  • 这与您在此处提出的问题不同。请提出您想回答的问题。
  • @Edward,在那种情况下,在执行 anything i>,在您的子类中实现它并在那里设置值?我将在我的答案中添加一个示例
  • @HåvardS 是的,我现在意识到这一点稍微改变了问题,即这就是我首先需要覆盖类变量的原因
  • @Edward 还回答了您关于名称隐藏与覆盖的问题,并提供了一个用例。
【解决方案2】:

静态或非虚拟方法或属性只是一个内存地址(为了简化事情)。虚拟方法或属性由表中的条目标识。该表取决于定义方法或属性的类。 当您覆盖派生类中的虚拟成员时,您实际上更改了派生类的表中的条目以指向覆盖方法。 在运行时,对此类成员的访问总是通过表。因此该条目可以被任何派生类覆盖。

字段没有这样的机制,因为它们旨在快速访问。

在成员上使用“新”意味着您不想覆盖表中的条目,但您想要一个新成员(与现有虚拟成员同名,如果您问我,这是一种不好的做法) .

如果您通过指向基类的指针访问虚拟成员,您将永远无法访问派生类中定义为“新”的成员,这是您问题第二部分中提到的区别。

【讨论】:

    【解决方案3】:

    而是为前缀成员创建一个属性 - 这样您可以将属性设置为虚拟/抽象

    字段用于存储对象的状态,它们帮助对象封装数据并隐藏其他人的实现问题。通过覆盖字段,我们将类的实现问题泄露给客户端代码(包括子类型)。由于这个原因,大多数语言都决定不能定义可以被覆盖的实例变量(尽管它们可以是公共的/受保护的......所以你可以访问它们)。

    您也不能将实例变量放在接口中

    【讨论】:

    • 你的意思是让它成为一个Getter(常规方法),比如protected string GetPrefix() { return "Secondary"; },是的,这可能是我要做的,它具有相同的功能但只是更多的代码,只是想知道它比上面的 hide-with-new 示例有什么优势。
    • 是的,但是您总是可以让父类在构造函数中使用前缀(由子类型传入),而在基类中只使用一个 getter - (编辑答案以涵盖一些问题)
    • 好吧,这让我想知道为什么首先可以使用 hide-with-new 选项,我觉得它在这种情况下对我有用的事实诱使我编写了糟糕的代码(隐藏内部继承的类变量),正如您所描述的,这只是对 hide-with-new 的不良使用还是完全避免 hide-with-new?
    • @Edward:在 C# 中,我建议您使用 Properties 而不是常规的 GetX 方法。
    • 使用新选项隐藏以便子类型可以再次使用该变量名(即使它在基类上是公共的)并导致对前缀的任何引用引用子类型自己的前缀成员从基类中暴露出来的那个。当使用与根基类前缀字段相对的前缀字段时,定义“新前缀”的原始子类型的任何更多派生子类型将引用新定义的字段
    【解决方案4】:

    在您的示例中,如果您没有在 SecondaryTransaction 类中覆盖“Show”,那么在 SecondaryTransaction 的实例上调用 Show 实际上将调用基类 (Transaction) 中的方法,因此将使用“Show”在基类中,导致输出:

    Primary: name1, state1 
    Primary: name1, state1
    

    因此,根据您调用的方法(即基类或子类中的方法),代码将具有不同的“前缀”值,这将是可维护性噩梦。我怀疑您可能想要/应该做的是在 Transaction 上公开一个包含“前缀”的属性。

    您不能覆盖字段,因为它是基类的实现细节。您可以更改受保护字段的 value,但通过覆盖它,您实际上是在说我要替换 field,而不是 value.

    我会做什么(如果我绝对不想/不能使用属性):

    public class Transaction
    {
        public string Name { get; set; }
        public string State { get; set; }
        protected string prefix = "Primary";
        public virtual string Show()
        {
            return String.Format("{0}: {1}, {2}", prefix, Name, State);
        }
    }
    public class SecondaryTransaction : Transaction
    { 
        public SecondaryTransaction()
        {
            prefix = "Secondary";
        }
        public override string Show()
        {
            return String.Format("{0}: {1}, {2}", prefix, Name, State);
        }
    }
    

    编辑:(根据我对另一个答案的评论)

    如果您调用基类的 ctor 并需要设置值,那么您可能必须修改 Transaction,可能像这样:

    public class Transaction
    {
        public string Name { get; set; }
        public string State { get; set; }
        protected string prefix = "Primary";
        // Declared as virtual ratther than abstract to avoid having to implement "TransactionBase"
        protected virtual void Initialise()
        { }
        public Transaction()
        {
            Initialise();
        }
        public virtual string Show()
        {
            return String.Format("{0}: {1}, {2}", prefix, Name, State);
        }
    }
    public class SecondaryTransaction : Transaction
    { 
        protected override void Initialise()
        {
            prefix = "Secondary";
        }
        public override string Show()
        {
            return String.Format("{0}: {1}, {2}", prefix, Name, State);
        }
    }
    

    【讨论】:

    • 好的,这是有教育意义的,我现在知道,在上述情况下,最好的办法是为每个我想要覆盖或保留在继承中的变量创建一个属性获取器类。我以为我终于在 C# 中找到了 hide-with-new 功能的用途,但如果这种使用方式非常糟糕,那么我不知道什么时候会在良好的实践中使用它。
    • @Edward,我从来没有发现它的用途。这并不意味着没有一个:)
    【解决方案5】:

    你为什么要覆盖一个受保护的变量,当然你想做的只是将它设置为覆盖类中的其他东西(可能在构造函数中)?

    【讨论】:

    • 这很常见:就像我继承一个类然后用自定义功能覆盖某些方法一样,我也想继承一个类并用某些值覆盖某些变量。如果这些小变量中的每一个都返回一个值,那么让它们中的每一个都成为完整的 getter 方法似乎有点过头了,如果我有 20 个,我希望它们都放在类顶部的一个块中,这样我就可以简单地覆盖我需要的那些某个继承类,不必通过 20 个扩展几个屏幕的 getter 方法来工作。
    • 如果是这种情况,那么我想知道您的设计是否可能不需要全部查看 - 并且看到您在这里制作的其他一些 cmets,我认为可能是这种情况.
    • 我从中得到的不是用 new 隐藏类变量,而是使用可以正确设置为虚拟和覆盖的 C# 属性,这是有道理的
    【解决方案6】:

    你不能,因为没有用。你会通过覆盖一个字段来完成什么?

    【讨论】:

      【解决方案7】:

      覆盖字段是无稽之谈。自动将字段标记为受保护可以在派生类中访问它们。您可以覆盖函数、属性,因为它在内部使用函数。

      【讨论】:

      • 如果这是一种不好的做法,为什么在 C# 中甚至可以隐藏一个带有 new 的字段?
      • 好的,我现在明白了,您将使用 hide-with-new 来为继承的方法 非虚拟 提供新功能,但它缺乏覆盖的优势因为继承类仍然会调用自己的方法。
      【解决方案8】:

      你不能,因为没有用。你会通过覆盖一个字段来完成什么? 简单:

      class Animal{
      
          public class Head {
              int eye = 8;
          } 
          Head head = new Head()
          public void Test() 
          { 
              print head.eye; 
              DoOtherStufs(); 
          } //8
      
          protected virtual void DoOtherStufs() 
          {}
      }
      
      class Cat : Animal{
          class new Head : Animal.Head 
          {
              int mouth;
          } 
          Head head = new Head()
          protected override void DoOtherStufs() 
          { 
              print head.eye; 
          }
      } 
      Cat cat = new Cat();
      
      cat.head.eye = 9;
      print cat.Test() 
      

      这将按预期打印 8 9 而不是 9 9。 我需要一个功能基类,但我还需要一个继承类,我可以操纵(增加)内部组类 vars 中的 vars。不可能!!

      【讨论】:

        猜你喜欢
        • 2015-09-09
        • 2020-04-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-02
        • 1970-01-01
        • 2012-01-06
        • 1970-01-01
        相关资源
        最近更新 更多