Why can't I use virtual/override on class vari

2020-08-11 04:06发布

问题:

In the following example I am able to create a virtual method Show() in the inherited class and then override it in the inheriting class.

I want to do the same thing with the protected class variable prefix but I get the error:

The modifier 'virtual' is not valid for this item

But since I can't define this variable as virtual/override in my classes, I get the compiler warning:

TestOverride234355.SecondaryTransaction.prefix' hides inherited member 'TestOverride234355.Transaction.prefix'. Use the new keyword if hiding was intended.

Luckily when I add the new keyword everything works fine, which is ok since I get the same functionality, but this raises two questions:

  1. Why I can use virtual/override for methods but not for protected class variables?

  2. What is the difference actually between the virtual/override approach and the hide-it-with-new approach since at least in this example they offer the same functionality?

Code:

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

回答1:

Overriding a field does not really make sense. It's part of the state of the base class, and if an inheriting class wishes to change it, it should be changable in the inheriting class by giving it an appropriate visibility.

One thing you could do in your case is to set prefix in the constructor for the inheriting class:

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

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

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

You can also make a property instead of a field, and make the property virtual. This will enable you to change the behavior of the getter and setter for the property in the inheriting class:

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

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

EDIT: As for your question of using a variable in a base constructor before an inheriting class has set it, one way to solve this is to define an initialization method in the base class, override it in the inheriting class, and call it from the base constructor before accessing any fields:

// 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";
  }
}

EDIT 2: You also asked what the difference between virtual/override and name hiding (the new keyword on methods) is, if it should be avoided, and if it can be useful.

Name hiding is a feature that breaks inheritance in the case of hiding virtual methods. I.e., if you hide the Initialize() method in the child class, the base class will not see it, and not call it. Also, if the Initialize() method was public, external code that was calling Initialize() on a reference of the base type would be calling Initialize() on the base type.

Name hiding is useful when a method is non-virtual in a base class, and a child wants to provide a different implementation of its own. Note, however, that this is NOT the same as virtual/override. References of the base type will call the base type implementation, and references of the child type will call the child type implementation.



回答2:

A static or non-virtual method or property is just a memory address (to simplify things). A virtual method or property is identified by an entry in a table. This table is dependent on the class defining the method or property. When you override a virtual member in a derived class, you actually change the entry in the table for the derived class to point to the overriding method. At run-time, access to such a member goes though the table, always. So the entry can be overridden by any derived class.

There is no such mechanism for fields, as they're meant to be accessed quickly.

Using 'new' on a member means that you do not want to override the entry in the table, but that you want a new member (with the same name as an existing virtual one, a bad practice if you ask me).

If you access a virtual member through a pointer to the base class, you'll never access the member defined as 'new' in the derived class, which is the difference mentioned in the second part of your question.



回答3:

Rather create a property for the prefix member - this way you can set the property to virtual/abstract

Fields are used to store state for an object, they help the object encapsulate data and hide implementation concerns from others. By being able to override a field we are leaking the implementation concerns of the class to client code (including subtypes). Due to this most languages have taken the decision that one cannot define instance variables that can be overridden (although they can be public/protected... so you can access them).

You also cannot put instance variables in an interface



回答4:

In your example, if you didn't override "Show" in the SecondaryTransaction class, then calling Show on an instance of SecondaryTransaction would actually be calling the method in the base class (Transaction), which would therefore use "Show" in the base class, resulting in output of:

Primary: name1, state1 
Primary: name1, state1

So, depending on what method you were calling (i.e. one on the base class or the child class), the code would have a different value for "prefix" which would be a maintainability nightmare. I suspect what you probably want to/should do, is expose a property on Transaction that wraps "prefix".

You can't override a field because it's an implementation detail of the base class. You can change the value of a protected field, but by overriding it you'd essentially be saying I want to replace the field, not the value.

What I would do (if I absolutely didn't want to/couldn't use properties) :

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

Edit: (As per my comment on another answer)

If you're calling down into your base class's ctor and need the value set, then you'll probably have to modify Transaction, possibly like this:

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


回答5:

Why do you want to override a protected variable, surely all you want to do is set it to something else in the overriding class (possibly in the constructor)?



回答6:

You can't because there is no use. What would you accomplish by overriding a field?



回答7:

Overriding a field is a nonsense. Marking field as protected you automatically may access them in derived classes. You may override functions, properties, because it uses functions internally.



回答8:

You can't because there is no use. What would you accomplish by overriding a field? Simple:

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() 

This will print 8 9 instead of 9 9 as expected. I need a base class functional, but I also need a inherited class with I could manipulate (increase) the vars inside internal group classes vars. Its not possible!!