给继承的只读字段赋值?

42

我有一个基类,有很多子类。这个基类定义了一些只读属性和变量,并给它们赋予默认值。因为每个子类的情况可能不同,所以这些默认值可能会不同。

只读属性/字段允许你在构造函数内和定义时更改变量的值,但是不能在其他地方更改。如果我尝试在子类的构造函数中更改继承的只读变量的值,就会出现“只读变量只能在构造函数中分配”的错误。为什么会这样?如何避免此问题,而不使用反射?

我的意图是:通过脚本允许用户进行可扩展性,使他们只能更改某些字段一次。


我曾经遇到过非常类似的问题,并且得到了一个答案,它允许您对成员进行单一赋值。在功能上是只读的,而且所有样板代码都在一个位置上。 - wizard07KSU
6个回答

39
由于只能在类的构造函数中对readonly字段进行赋值,所以出现了这种情况。
根据C#参考文档中readonly的定义(重点在我):https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly

当一个字段声明包含readonly修饰符时,通过该声明引入的字段的赋值仅可以作为声明的一部分或在同一类的构造函数中发生。

为了解决这个问题,您可以在基类中创建一个受保护的构造函数,该构造函数需要带有readonly属性的参数。

例如:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Base b = new Child();
            Console.WriteLine(b.i);
            Console.Read();
        }
    }

    class Base
    {
        public readonly int i;

        public Base()
        {
            i = 42;
        }

        protected Base(int newI)
        {
            i = newI;
        }
    }

    class Child : Base
    {
        public Child()
            : base(43)
        {}
    }
}

10
这种解决方案唯一的缺点是它不允许您计算要传递给基类的值。 - Kent
7
可以通过在基类构造函数的参数列表中调用静态方法来实现。请参见https://dev59.com/3XI-5IYBdhLWcg3w1sPi。 - ofthelit

10

通过使用虚拟的只读属性,您可以获得您所期望的精确行为。

public class BSE
{
    virtual public int Prop 
    {
        get
        {
            return 6;
         }
     }
}
public class Derived : BSE
{
    public override int Prop
    {
         get
         {
             return 10;
         }
    }
 }

字段在继承和重载模型之外,不应该用来提供多态特性。


这听起来更像它了;但是与干净的 public readonly int number = 5 相比,仍然是一个相当大的跳跃。 - Steffan Donal
@Ruirize 你要把一个常量赋值给只读字段?这真的听起来像是滥用只读字段,你可能更好地使用虚拟属性。 - CodesInChaos

10

Adam给出了正确的答案。如果你担心构造函数中参数数量会占用太多空间,那么你应该把这个问题作为一个独立问题考虑,并采用不同的解决方案:创建一个BaseConfig类,其中包含所有这些属性,这是唯一需要传递的内容。Base可以从BaseConfig的属性分配所有只读字段,或者您可以让Base仅持有一个BaseConfig类型的只读字段,并引用它来获取值。

至于为什么要这样做,请参见C# constructor execution order,了解每个类的只读字段何时被初始化或可初始化的情况。


这让我有点难过;我本来希望有一种非常好的实现方式,这样为我的引擎编写脚本就会变得非常整洁和聪明。哦,算了。一个BaseConfig类同样可以胜任,谢谢! - Steffan Donal

2

您可以使用具有公共get访问器和受保护set访问器的属性。派生类可以设置此属性的值。

例如:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Base b = new Child();
            Console.WriteLine(b.I);
            Console.Read();
        }
    }

    class Base
    {
        public int I { get; protected set; }

        public Base()
        {
            I = 42;
        }
    }

    class Child : Base
    {
        public Child()
        {
            I = 43;
        }
    }
}

1
protected 不是只读的。派生类可以在其构造函数之外设置它。 - joe

0

这是设计上不可能的。尝试将值传递给一个protected基类构造函数。


0
这基本上就是@rerun提出的,但是使用abstract代替virtual
protected abstract class BaseClass
{
    protected abstract int PrivateI { get; }
}

public class DerivedClass : BaseClass
{
    private readonly int _i;
    protected override int PrivateI => _i;

    public DerivedClass(int i)
    {
        _i = i;
    }
}

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