繁体   English   中英

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

[英]Why can't I use virtual/override on class variables as I can on methods?

在下面的示例中,我能够在继承的类中创建一个方法Show() ,然后在继承类中重写它。

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

修饰符'virtual'对此项无效

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

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

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

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

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

码:

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

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

在您的情况下,您可以做的一件事是在继承类的构造函数中设置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()

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

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

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

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

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

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

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

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

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

 Primary: name1, state1 Primary: name1, state1 

因此,根据您调用的方法(即基类或子类中的一个),代码将具有不同的“前缀”值,这将是可维护性的噩梦 我怀疑你可能想要/应该做什么,是在包含“前缀”的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);
    }
}

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

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

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

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

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。 我需要一个基类函数,但我还需要一个继承类,我可以操作(增加)内部组类变量中的变量。 这是不可能的!!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM