我试图在C#中自学OOP,但我对何时使用base
有疑问。我理解一般原则,但我不确定下面的例子中最好的是什么。这个简单的测试包括:
interface
有两个string
属性abstract
类,并添加了几个string
属性base
而另一个不使用,但是在程序执行时它们都产生相同的输出。我的问题是:在这个例子中,一个实现比另一个更理想?我不确定TranslationStyleA
和TranslationStyleB
之间是否存在任何有意义的差异,或者它是否仅仅取决于个人偏好?
非常感谢您的时间和想法!
using System;
namespace Test
{
interface ITranslation
{
string English { get; set; }
string French { get; set; }
}
public abstract class Translation : ITranslation
{
public virtual string English { get; set; }
public virtual string French { get; set; }
public string EnglishToFrench { get { return English + " is " + French + " in French"; } }
public string FrenchToEnglish { get { return French + " is " + English + " in English"; } }
public Translation(string e, string f)
{
English = e;
French = f;
}
}
public class TranslationStyleA : Translation
{
public override string English
{
get { return base.English; }
set { base.English = value; }
}
public override string French
{
get { return base.French; }
set { base.French = value; }
}
public TranslationStyleA(string e, string f) : base(e, f)
{
}
}
public class TranslationStyleB : Translation
{
private string english;
public override string English
{
get { return english; }
set { english = value; }
}
private string french;
public override string French
{
get { return french; }
set { french = value; }
}
public TranslationStyleB(string e, string f) : base(e, f)
{
this.English = e;
this.French = f;
}
}
class Program
{
static void Main(string[] args)
{
TranslationStyleA a = new TranslationStyleA("cheese", "fromage");
Console.WriteLine("Test A:");
Console.WriteLine(a.EnglishToFrench);
Console.WriteLine(a.FrenchToEnglish);
TranslationStyleB b = new TranslationStyleB("cheese", "fromage");
Console.WriteLine("Test B:");
Console.WriteLine(b.EnglishToFrench);
Console.WriteLine(b.FrenchToEnglish);
Console.ReadKey();
}
}
}
答案 0 :(得分:4)
您需要了解的第一件事是当您拥有自动属性时会发生什么:
public virtual string English { get; set; }
在幕后,编译器正在生成一个私有字段,并在您访问该属性时获取/设置该私有字段。它相当于这个
private string _english;
public virtual string English { get { return _english; } set { _english = value; } }
除了您不知道私有字段的名称,因此您无法访问它。
所以在你的TranslationStyleA
类中,你实际上并没有对英语属性做任何事情,因为它只是直接访问基类的属性而不改变它的行为。
// None of this is even needed- we are just delegating to the base class
public override string English
{
get { return base.English; }
set { base.English = value; }
}
现在在TranslationStyleB
类中,您实际上正在改变属性的行为(尽管以相当无用的方式)。您不是将英语属性的值存储在基类的自动实现的私有变量中,而是将其存储在派生类级别定义的私有变量中:
private string english;
public override string English
{
get { return english; }
set { english = value; }
}
这些实现当然都没有做任何事情,并且既不需要实现,因为基类本身实现了完美的属性。因此,我对原始问题的回答是,根据您描述的代码,两者都不是首选。
现在,让我们看一个您的问题相关的示例。例如,如果您想更改他们的行为,您只需要覆盖它们。
// We don't want any leading or trailing whitespace, so we remove it here.
public override string English
{
get { return base.English; }
set { base.English = value.Trim(); }
}
我们想在这里委托基类,因为这些属性首先是属性。在语义上,属性与字段相同:
public String Foo;
public String Foo { get; set; } // <-- why bother with all this extra { get; set; } stuff?
原因是从编译器的角度来看,从属性到字段的界面是一个重大变化。所以,如果我改变
public String Foo;
到
public String Foo { get; set; }
然后需要重新编译依赖于我的代码的任何代码。但是,如果我改变
public String Foo { get; set; }
到
private string _foo;
public String Foo { get { return _foo; } set { _foo = value.Trim(); } }
然后依赖代码仍然只看到公共属性,并且不需要重新编译(因为我的类的接口没有改变)。
如果此处的基类(Translation
)要改变它对属性English
的行为,那么:
private string _english;
public String English { get { return _english; } set { _english = value.ToUpper(); } }
您希望在派生类中选择它!
因此,考虑到属性具有与之关联的行为,您应始终委托父类实现,除非该实现在派生类中具有不良影响。
答案 1 :(得分:3)
除非你有充分的理由选择另一种风格,否则第一种风格绝对是可取的。
Translation
的自动实现属性各自添加一个字段,样式B添加更多而不是使用编译器添加的字段。样式A重用编译器添加的样式,节省了一些存储空间。
此外,如果您不打算更改其功能,则无需覆盖超类的属性。你甚至可以写下这样的另一种风格:
public class TranslationStyleC : Translation {
public TranslationStyleC(string e, string f) : base(e, f) {
}
}
答案 2 :(得分:2)
您实际上不需要覆盖任何超类属性来实现您想要的效果,因为您不会以任何方式增强超类行为。
如果从基础abstract
中删除Translation
修饰符,则不再需要子类,因为它在功能上与两者都相同。
现在,关于何时使用base
;当您想要访问子类中已被重写的超类中的功能时,您应该使用它。 base
调用始终在编译时静态绑定到超类方法 ;即使超类方法是virtual
(如你的情况)。对于base
次调用可能发生的奇怪事情,请查看here。
答案 3 :(得分:1)
如前所述,样式A 重用已声明的字段,而样式B 声明新字段。关于何时使用 base 的问题,经验法则是“只要您想重用父类中定义的逻辑/代码”。
答案 4 :(得分:1)
它确实归结为您打算如何利用您的构造。
实现后,TranslationStyleA上被覆盖的成员有点多余,因为消费者可以轻松访问基本成员而不提供基本派生中的覆盖。在这些情况下,如果这样做不会给设计增加任何价值,我个人就不会打扰覆盖基本成员。
当您真正想要覆盖基类成员的设置和访问时,第二个实现很常见,例如,如果基类成员的设置是启动另一个操作的催化剂,那么派生的覆盖成员将是适合发生这种情况的地方。