不确定何时在 C# 中使用"base"



我正在尝试自学C#中的OOP,但是我有一个关于何时使用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();
        }
    }
}

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

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;

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

你会想在你的派生类中拾取它!

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

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

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

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

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

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

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

现在,关于何时使用 base ;当你想访问在子类中被覆盖的超类中的功能时,你应该使用它。 base调用始终在编译时静态绑定到超类方法;即使超类方法是virtual的(如您的情况)。有关base电话可能发生的奇怪事情,请查看此处。

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

它确实取决于您打算如何利用您的构造。

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

当您真正想要重写基类成员的设置

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

相关内容

  • 没有找到相关文章

最新更新