在以下示例中,我可以在继承的类中创建虚拟方法Show()
,然后覆盖 继承类。
我想使用受保护的类变量 prefix
执行相同的操作但是我收到错误:
修饰符'virtual'无效 这个项目
但是因为我无法在我的类中将此变量定义为虚拟/覆盖,所以我得到编译器警告:
TestOverride234355.SecondaryTransaction.prefix” 隐藏继承的成员 'TestOverride234355.Transaction.prefix'。 如果隐藏了,请使用new关键字 意图。
幸运的是,当我添加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);
}
}
}
答案 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。这不可能!