不确定何时在C#中使用“base”关键字

3

我正在尝试自学C#中的面向对象编程,但我有一个关于何时使用base的问题。我理解一般原则,但不确定在下面的示例中哪种方法更好。这个简单的测试包括:

  • 一个具有两个string属性的interface
  • 实现此接口并添加了几个其他string属性的abstract
  • 两个实现抽象类的类。其中一个使用base,另一个则没有,但它们在执行程序时都会产生相同的输出。

我的问题是:在这个例子中,是否有一种实现比另一种更可取?我不确定TranslationStyleATranslationStyleB之间是否有任何有意义的区别,或者只是个人喜好?

非常感谢您的时间和想法!

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();
        }
    }
}
5个回答

5

你需要理解的第一件事情是,当你拥有一个自动属性时发生了什么:

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

你应该在派生类中选择它!

考虑到属性具有与之相关的行为,除非该实现对派生类产生不良影响,否则您应始终委托给父类实现。


谢谢Chris!虽然所有的答案都很好,但你真的讲得非常清楚。我现在明白了幕后发生的事情以及为什么AB会更可取。再次感谢您快速详细的回复! - Superangel

4
第一种样式肯定更可取,除非你有一些好的理由选择另一种方式。 Translation 的自动实现属性会添加一个字段,而B样式添加更多的字段,而不是使用编译器添加的字段。A样式重用了编译器添加的字段,节省了一些存储空间。
此外,如果您不打算改变它们的功能,则没有必要覆盖超类的属性。您甚至可以编写另一种样式,如下所示:
public class TranslationStyleC : Translation {
    public TranslationStyleC(string e, string f) : base(e, f) {
    }
}

谢谢icktoofay,非常清晰明了。我真的很感激你的帮助! - Superangel

3

你实际上不需要覆盖任何超类属性以达到你想要的效果,因为你没有以任何方式增强超类行为。

如果你从基础 Translation 中删除 abstract 修饰符,你就不再需要子类了,因为它的功能将与两者等效。

现在,关于何时使用 base;当你想要访问在子类中被重写的超类中的功能时,应该使用它。无论超类方法是否是虚拟的(如你的情况),base 调用始终在编译时静态绑定到超类方法。对于可以在 base 调用中发生的奇怪事情,请查看 这里


谢谢Jordão!你说得对,我可能把我的例子简化了,但现在我明白了发生了什么细节。再次感谢你的帮助! - Superangel
不客气!我添加了更多关于何时使用“base”及其特殊性的信息... - Jordão

2

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


谢谢Ulises,非常清晰简洁。我感激你的帮助! - Superangel

1

这取决于您打算如何利用您的构造。

就实现而言,TranslationStyleA上重写的成员有点多余,因为消费者可以很容易地访问基本成员,而不必在基本派生中提供覆盖。在这种情况下,如果没有增加任何设计价值,我个人不会费心去覆盖基本成员。

第二种实现方式通常用于真正想要覆盖基类成员的情况,例如,如果设置基类成员是启动另一个操作的催化剂,则派生中的覆盖成员将是适当的位置。


感谢分享,KodeKreachor,这真的很有道理。 - Superangel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接