为什么我不能像在方法上那样对类变量使用virtual / override?

时间:2010-03-04 10:16:27

标签: c# oop virtual override new-operator

在以下示例中,我可以在继承的类中创建虚拟方法Show(),然后覆盖 继承类。

我想使用受保护的类变量 prefix执行相同的操作但是我收到错误:

  

修饰符'virtual'无效   这个项目

但是因为我无法在我的类中将此变量定义为虚拟/覆盖,所以我得到编译器警告

  

TestOverride234355.SecondaryTransaction.prefix”   隐藏继承的成员   'TestOverride234355.Transaction.prefix'。   如果隐藏了,请使用new关键字   意图。

幸运的是,当我添加new关键字时,一切正常,这是好的,因为我获得了相同的功能,但这引发了两个问题

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

  2. 虚拟/覆盖方法与隐藏新方法之间的实际区别是什么,因为至少在此示例中它们提供相同的功能?

  3. 代码:

    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);
            }
        }
    }
    

8 个答案:

答案 0 :(得分:8)

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

您可以做的一件事是在继承类的构造函数中设置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:您还询问了虚拟/覆盖和名称隐藏(方法上的新关键字)之间的区别,是否应该避免,以及它是否有用。

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

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

答案 1 :(得分:3)

而是为前缀成员创建一个属性 - 这样就可以将属性设置为virtual / abstract

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

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

答案 2 :(得分:2)

静态或非虚拟方法或属性只是一个内存地址(为了简化操作)。虚拟方法或属性由表中的条目标识。此表依赖于定义方法或属性的类。 当您在派生类中重写虚拟成员时,实际上更改了表中的条目以使派生类指向重写方法。 在运行时,访问这样的成员总是在表中。因此,任何派生类都可以覆盖该条目。

字段没有这种机制,因为它们可以快速访问。

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

如果通过指向基类的指针访问虚拟成员,则永远不会访问派生类中定义为“new”的成员,这是问题第二部分中提到的差异。

答案 3 :(得分:1)

在您的示例中,如果您未在SecondaryTransaction类中重写“Show”,则在SecondaryTransaction实例上调用Show实际上将调用基类(Transaction)中的方法,因此将使用“Show”在基类中,产生输出:

Primary: name1, state1 
Primary: name1, state1

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

您无法覆盖字段,因为它是基类的实现细节。您可以更改受保护字段的,但是通过覆盖它,您基本上就是说我要替换字段,而不是

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

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);
    }
}

答案 4 :(得分:0)

为什么要覆盖受保护的变量,当然您只想将其设置为覆盖类中的其他内容(可能在构造函数中)?

答案 5 :(得分:0)

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

答案 6 :(得分:0)

覆盖一个字段是胡说八道。将字段标记为受保护您自动可以在派生类中访问它们。您可以覆盖函数,属性,因为它在内部使用函数。

答案 7 :(得分:0)

不能,因为没有用。通过覆盖某个字段,您将完成什么工作? 简单:

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。这不可能!