我有一个抽象基类,我想声明一个字段或属性,在每个继承自此父类的类中具有不同的值。
我想在基类中定义它,所以我可以在基类方法中引用它 - 例如重写ToString来说“此对象的类型为 property / field ”。 我有三种方法可以看到这样做,但我想知道 - 这样做的最佳或可接受的方式是什么?新手问题,对不起。
选项1:
使用抽象属性并在继承的类上覆盖它。这得益于强制执行(你必须覆盖它)并且它是干净的。但是,返回硬代码值而不是封装字段感觉有点不对,而且它只是几行代码而不仅仅是。我还必须为“集合”声明一个主体,但这不太重要(并且可能有一种方法可以避免我不知道的情况)。
abstract class Father
{
abstract public int MyInt { get; set;}
}
class Son : Father
{
public override int MyInt
{
get { return 1; }
set { }
}
}
选项2
我可以声明一个公共字段(或受保护的字段)并在继承的类中显式覆盖它。下面的例子会给我一个使用“new”的警告,我可能会这样做,但它感觉不对,它打破了多态性,这是重点。看起来不是一个好主意...
abstract class Mother
{
public int MyInt = 0;
}
class Daughter : Mother
{
public int MyInt = 1;
}
选项3
我可以使用受保护的字段并在构造函数中设置值。这看起来很整洁,但依赖于我确保构造函数始终设置这个并且使用多个重载的构造函数总是有可能某些代码路径不会设置该值。
abstract class Aunt
{
protected int MyInt;
}
class Niece : Aunt
{
public Niece()
{
MyInt = 1;
}
}
这是一个理论上的问题,我想答案必须是选项1,因为它是唯一的安全选项,但我只是想要掌握C#,并想问这个人有更多的经验。
答案 0 :(得分:127)
在三种解决方案中,只有选项1 是多态。
字段本身不能被覆盖。这就是为什么选项2 会返回 新 关键字警告的原因。
警告的解决方案不是附加“new”关键字,而是实施选项1.
如果您需要将字段设置为多态,则需要将其包装在属性中。
如果您不需要多态行为,选项3 就可以了。您应该记住,在运行时访问属性MyInt时,派生类无法控制返回的值。基类本身能够返回该值。
这是属性的真正多态实现的样子,允许派生类在控件中。
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 */ }
}
}
答案 1 :(得分:17)
选项2是非首发 - 您无法覆盖字段,您只能隐藏。
就个人而言,我每次都会选择选项1。我总是试着让田地保持私密。当然,如果你真的需要能够覆盖这个属性的话。另一个选项是在基类中具有只读属性,该属性是根据构造函数参数设置的:
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 :(得分:7)
选项2是一个坏主意。它会产生一种称为阴影的东西;基本上你有两个不同的“MyInt”成员,一个在母亲身边,另一个在女儿身上。问题在于,母亲实施的方法将引用母亲的“MyInt”,而女儿实施的方法将引用女儿的“MyInt”。这可能会导致一些严重的可读性问题,以及后来的混乱。
就个人而言,我认为最好的选择是3;因为它提供了一个清晰的集中值,并且可以由儿童在内部引用,而无需定义自己的字段 - 这是选项1的问题。
答案 3 :(得分:4)
你可以定义这样的东西:
abstract class Father
{
//Do you need it public?
protected readonly int MyInt;
}
class Son : Father
{
public Son()
{
MyInt = 1;
}
}
通过将值设置为readonly,可确保该类的值在对象的生命周期内保持不变。
我想下一个问题是:你为什么需要它?
答案 4 :(得分:3)
你可以这样做
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允许你获得一个属性,该属性可以执行某些操作,并且仍允许子类覆盖它。
答案 5 :(得分:1)
如果您正在构建一个类,并且希望该属性具有一个基值,则在基类中使用virtual关键字。这使您可以选择覆盖该属性。
使用上面的示例:
//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;
}
}
}
在程序中:
Son son = new Son();
//son.MyInt will equal 2
答案 6 :(得分:0)
我选择3,但是有一个抽象的setMyInt方法,子类被强制实现。这样你就不会遇到派生类忘记在构造函数中设置它的问题。
abstract class Base
{
protected int myInt;
protected abstract void setMyInt();
}
class Derived : Base
{
override protected void setMyInt()
{
myInt = 3;
}
}
顺便说一句,如果你没有指定set,那么使用选项1;在您的抽象基类属性中,派生类不必实现它。
abstract class Father
{
abstract public int MyInt { get; }
}
class Son : Father
{
public override int MyInt
{
get { return 1; }
}
}
答案 7 :(得分:0)
如果修改抽象基类以在构造函数中要求属性值,则可以使用选项3,不会遗漏任何路径。我真的考虑过这个选择。
abstract class Aunt
{
protected int MyInt;
protected Aunt(int myInt)
{
MyInt = myInt;
}
}
当然,您仍然可以选择将字段设为私有,然后根据需要公开受保护或公共属性的getter。
答案 8 :(得分:0)
我这样做了......
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";
}
}
}
这样你仍然可以使用字段。
答案 9 :(得分:0)
当您想要一个带有实现的抽象类时的示例实现。子类必须:
在这种情况下,除了抽象类及其自己的子类之外,实现必需的属性不可用。
internal abstract class AbstractClass
{
//Properties for parameterization from concrete class
protected abstract string Param1 { get; }
protected abstract string Param2 { get; }
//Internal fields need for manage state of object
private string var1;
private string var2;
internal AbstractClass(string _var1, string _var2)
{
this.var1 = _var1;
this.var2 = _var2;
}
internal void CalcResult()
{
//The result calculation uses Param1, Param2, var1, var2;
}
}
internal class ConcreteClassFirst : AbstractClass
{
private string param1;
private string param2;
protected override string Param1 { get { return param1; } }
protected override string Param2 { get { return param2; } }
public ConcreteClassFirst(string _var1, string _var2) : base(_var1, _var2) { }
internal void CalcParams()
{
//The calculation param1 and param2
}
}
internal class ConcreteClassSecond : AbstractClass
{
private string param1;
private string param2;
protected override string Param1 { get { return param1; } }
protected override string Param2 { get { return param2; } }
public ConcreteClassSecond(string _var1, string _var2) : base(_var1, _var2) { }
internal void CalcParams()
{
//The calculation param1 and param2
}
}
static void Main(string[] args)
{
string var1_1 = "val1_1";
string var1_2 = "val1_2";
ConcreteClassFirst concreteClassFirst = new ConcreteClassFirst(var1_1, var1_2);
concreteClassFirst.CalcParams();
concreteClassFirst.CalcResult();
string var2_1 = "val2_1";
string var2_2 = "val2_2";
ConcreteClassSecond concreteClassSecond = new ConcreteClassSecond(var2_1, var2_2);
concreteClassSecond.CalcParams();
concreteClassSecond.CalcResult();
//Param1 and Param2 are not visible in main method
}