C# 不确定何时使用';基数';在C中#

C# 不确定何时使用';基数';在C中#,c#,oop,interface,abstract-class,base,C#,Oop,Interface,Abstract Class,Base,我试图自学C#中的OOP,但我有一个问题,关于何时使用base。我理解一般原则,但我不确定下面的例子中什么是最好的。这个简单的测试包括: 具有两个字符串的接口 一个抽象类,它实现了这个接口并添加了更多的字符串属性 实现抽象类的两个类。一个使用base,另一个不使用,但它们在执行程序时都会产生相同的输出 我的问题是:在本例中,一个实现是否比另一个更理想?我不确定TranslationStyleA和TranslationStyleB之间是否有任何有意义的区别,或者这只是个人偏好的问题 非常感谢您

我试图自学C#中的OOP,但我有一个问题,关于何时使用
base
。我理解一般原则,但我不确定下面的例子中什么是最好的。这个简单的测试包括:

  • 具有两个
    字符串的
    接口
  • 一个
    抽象
    类,它实现了这个接口并添加了更多的
    字符串
    属性
  • 实现抽象类的两个类。一个使用
    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();
        }
    }
}

第一种风格当然更可取,除非你有充分的理由选择另一种

Translation
的自动实现属性每个都添加了一个字段,样式B添加了更多的字段,而不是使用编译器添加的字段。样式A重用编译器添加的样式,节省了一些存储空间

此外,如果不打算更改超类的功能,则无需重写超类的属性。你甚至可以写另一种风格,像这样:

public class TranslationStyleC : Translation {
    public TranslationStyleC(string e, string f) : base(e, f) {
    }
}

您实际上不需要重写任何超类属性来实现您想要的效果,因为您没有以任何方式增强超类行为

如果从基
Translation
中删除
abstract
修饰符,则不再需要子类,因为它在功能上等同于这两个类


现在,关于何时使用
base
;当您想要访问在子类中被重写的超类中的功能时,应该使用它<代码>基
调用总是在编译时静态绑定到超类方法;即使超类方法是虚拟的(如您的情况)。有关
base
调用可能发生的奇怪事情,请看一看。

您需要了解的第一件事是当您拥有自动属性时会发生什么:

public virtual string English { get; set; }
在幕后,编译器生成一个私有字段,并在访问属性时获取/设置该私有字段。相当于这个

private string _english;
public virtual string English { get { return _english; } set { _english = value; } }
除了您不知道私有字段的名称,因此无法访问它

因此,在您的
TranslationStyleA
类中,您实际上并没有对English属性做任何操作,因为它只是直接访问基类的属性,并且不会改变它的行为

    // 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
类中,您实际上正在更改属性的行为(尽管是以一种相当无用的方式)。不是将English属性的值存储在基类的自动实现的私有变量中,而是将其存储在派生类级别定义的私有变量中:

    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 { get; set; }

然后依赖代码仍然只看到公共属性,不需要重新编译(因为我的类的接口没有更改)

如果此处的基类(
Translation
)要更改属性
English
的行为,则:

private string _english;
public String English { get { return _english; } set { _english = value.ToUpper(); } }
您可能希望在派生类中获取这些信息


因此,考虑到属性具有与其相关联的行为,您应该始终委托给父类实现,除非该实现在派生类中有不良影响。

如前所述,样式A重用已声明的字段,而样式B声明新字段。关于何时使用base的问题,经验法则是“只要您想重用父类中定义的逻辑/代码”

这取决于你打算如何利用你的结构

在实现时,TranslationStyleA上被重写的成员有点多余,因为使用者可以轻松访问基本成员,而无需在基本派生中提供重写。在这样的情况下,如果这样做不会给设计增加任何价值,我个人根本不会费心重写基本成员


第二种实现在您确实希望覆盖基类成员的设置和访问时很常见,例如,如果基类成员的设置是启动另一个操作的催化剂,那么派生上被覆盖的成员将是发生这种情况的合适位置。

谢谢Chris!虽然所有的答案都很好,但你确实解释得很清楚。我现在明白了幕后发生的事情,以及为什么
A
B
更可取。再次感谢您快速详细的回复!谢谢icktoofay,这很清楚。我真的很感谢你的帮助!谢谢Jordão!你是对的,我可能把我的例子简化得太多了,但我现在明白了发生的细节。再次感谢你的帮助!不客气!我补充了一些关于t的更多信息
public String Foo { get; set; }
private string _foo;
public String Foo { get { return _foo; } set { _foo = value.Trim(); } }
private string _english;
public String English { get { return _english; } set { _english = value.ToUpper(); } }