关于c#:覆盖子类中的字段或属性

Overriding fields or properties in subclasses

我有一个抽象基类,我想声明一个字段或属性,该字段或属性将在每个继承自父类的类中具有不同的值。

我想在基类中定义它,这样我就可以在基类方法中引用它——例如,重写ToString以说"这个对象的类型是属性/字段"。我有三种方法可以做到这一点,但我想知道——什么是最好的或公认的方法?新手问题,对不起。

选项1:使用抽象属性并在继承的类上重写它。这得益于强制执行(你必须覆盖它),而且它是干净的。但是,返回一个硬代码值而不是封装一个字段感觉有点错误,它只是几行代码而不是简单的代码。我还必须为"set"声明一个身体,但这并不重要(可能有一种方法可以避免我不知道的事情)。

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class Father
{
    abstract public int MyInt { get; set;}
}

class Son : Father
{
    public override int MyInt
    {
        get { return 1; }
        set { }
    }
}

选项2我可以声明一个公共字段(或一个受保护的字段),并在继承的类中显式重写它。下面的示例将警告我使用"new",我可能会这样做,但感觉不对,它打破了多态性,这就是关键所在。似乎不是个好主意…

1
2
3
4
5
6
7
8
9
abstract class Mother
{
    public int MyInt = 0;
}

class Daughter : Mother
{
    public int MyInt = 1;
}

选项3我可以使用一个受保护的字段并在构造函数中设置值。这看起来很整洁,但我要确保构造函数总是设置这个值,并且对于多个重载的构造函数,总是有可能某些代码路径不会设置该值。

1
2
3
4
5
6
7
8
9
10
11
12
abstract class Aunt
{
    protected int MyInt;
}

class Niece : Aunt
{
    public Niece()
    {
        MyInt = 1;
    }
}

这是一个理论上的问题,我想答案必须是选项1,因为它是唯一安全的选项,但我只是想和C打交道,并想问更多经验的人这个问题。


在这三种解决方案中,只有选项1是多态的。

不能重写字段本身。这就是为什么选项2返回新的关键字警告。

警告的解决方案不是附加"new"关键字,而是实现选项1。

如果你需要你的字段是多态的,你需要把它包装在一个属性中。

如果您不需要多态行为,选项3是可以的。不过,您应该记住,在运行时访问属性myint时,派生类对返回的值没有控制权。基类本身能够返回这个值。

这就是您的属性的真正的多态实现的外观,允许派生类处于控制之中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abstract class Parent
{
    abstract public int MyInt { get; }
}

class Father : Parent
{
    public override int MyInt
    {
        get { /* Apply formula"X" and return a value */ }
    }
}

class Mother : Parent
{
    public override int MyInt
    {
        get { /* Apply formula"Y" and return a value */ }
    }
}


选项2是非启动项-您不能覆盖字段,只能隐藏它们。

就我个人而言,我每次都会选择1。我试着随时保持田地的私密。当然,如果你真的需要重写这个属性的话。另一个选项是在从构造函数参数设置的基类中具有只读属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Mother
{
    private readonly int myInt;
    public int MyInt { get { return myInt; } }

    protected Mother(int myInt)
    {
        this.myInt = myInt;
    }
}

class Daughter : Mother
{
    public Daughter() : base(1)
    {
    }
}

如果值在实例的生命周期中不发生变化,那么这可能是最合适的方法。


选项2是个坏主意。它会产生一种叫做阴影的东西;基本上你有两个不同的"我的"成员,一个在母亲身上,另一个在女儿身上。问题在于,在母亲中实现的方法将引用母亲的"myint",而在女儿中实现的方法将引用女儿的"myint"。这可能会导致一些严重的可读性问题,并在后期造成混乱。

就我个人而言,我认为最好的选择是3;因为它提供了一个清晰的集中值,并且儿童可以在内部引用它,而不必费心定义自己的字段——这是选项1的问题。


你可以这样定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class Father
{
    //Do you need it public?
    protected readonly int MyInt;
}

class Son : Father
{
    public Son()
    {
        MyInt = 1;
    }
}

通过将该值设置为只读,它可以确保该类的值在对象的生命周期内保持不变。

我想下一个问题是:你为什么需要它?


你可以这样做

1
2
3
4
5
6
7
8
9
10
11
class x
{
    private int _myInt;
    internal virtual int myInt { get { return _myInt; } set { _myInt = value; } }
}

class y : x
{
    private int _myYInt;
    public override int myInt { get { return _myYInt; } set { _myYInt = value; } }
}

虚拟允许您获得一个属性,一个执行某些操作的主体,并且仍然允许子类覆盖它。


如果您正在构建一个类,并且希望该属性有一个基值,那么请在基类中使用virtual关键字。这允许您选择性地重写属性。

使用上面的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//you may want to also use interfaces.
interface IFather
{
    int MyInt { get; set; }
}


public class Father : IFather
{
    //defaulting the value of this property to 1
    private int myInt = 1;

    public virtual int MyInt
    {
        get { return myInt; }
        set { myInt = value; }
    }
}

public class Son : Father
{
    public override int MyInt
    {
        get {

            //demonstrating that you can access base.properties
            //this will return 1 from the base class
            int baseInt = base.MyInt;

            //add 1 and return new value
            return baseInt + 1;
        }
        set
        {
            //sets the value of the property
            base.MyInt = value;
        }
    }
}

在一个程序中:

1
2
Son son = new Son();
//son.MyInt will equal 2


我做到了…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace Core.Text.Menus
{
    public abstract class AbstractBaseClass
    {
        public string SELECT_MODEL;
        public string BROWSE_RECORDS;
        public string SETUP;
    }
}

namespace Core.Text.Menus
{
    public class English : AbstractBaseClass
    {
        public English()
        {
            base.SELECT_MODEL ="Select Model";
            base.BROWSE_RECORDS ="Browse Measurements";
            base.SETUP ="Setup Instrument";
        }
    }
}

这样您仍然可以使用字段。


如果修改抽象基类以要求构造函数中的属性值,则可以使用选项3,这样不会丢失任何路径。我真的会考虑这个选择。

1
2
3
4
5
6
7
8
9
abstract class Aunt
{
    protected int MyInt;
    protected Aunt(int myInt)
    {
        MyInt = myInt;
    }

}

当然,您仍然可以选择将字段设置为私有,然后根据需要公开受保护或公共财产获取者。


我将使用选项3,但是有一个抽象的setmyint方法,子类被强制实现。这样就不会出现派生类忘记在构造函数中设置它的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class Base
{
 protected int myInt;
 protected abstract void setMyInt();
}

class Derived : Base
{
 override protected void setMyInt()
 {
   myInt = 3;
 }
}

顺便说一句,对于选项1,如果不指定set;在抽象基类属性中,派生类将不必实现它。

1
2
3
4
5
6
7
8
9
10
11
12
abstract class Father
{
    abstract public int MyInt { get; }
}

class Son : Father
{
    public override int MyInt
    {
        get { return 1; }
    }
}