'private static readonly' 字段和静态/非静态构造函数

5

我有三个对象:

private static readonly Apple a, c;
private readonly Orange b;

这段代码是从我的构造函数中调用的:

public SomeClass()
{
    a = new Apple();
    b = new Orange(a.getDna());
    c = new Apple(b.getDna());
}

当我使用静态只读修饰符时,会出现“只读字段不能用作赋值目标”的错误。如果我删除其中一个修饰符,则可以编译成功。(这里有错误警告吗?)

在查看其他 SO 答案时,我发现应该使用静态构造函数,例如:

static SomeClass()
{
    a = new Apple();
    c = new Apple(b.getDna());
}

public SomeClass()
{
    b = new Orange(a.getDna());
}

但这样会导致静态构造函数首先被调用,并且由于b未初始化而导致错误。
我该如何规避这个问题?
附言:我相对较新于C#。

通常情况下,当创建类的新实例并调用特定的构造函数时,静态方法首先被调用,然后再调用第二个。但是静态构造函数在应用程序初始化时被调用。 - Peyman
静态构造函数将首先被调用,并且它将在您第一次访问该类型时被调用。问题在于,为什么您想在静态上下文中访问实例变量(c = new Apple(b.getDna());)?这没有意义(并且会引发编译时错误),并且从远处就能闻到气味。要访问b,您需要一个实例,而在静态上下文中您没有实例(没有this)。您应该重新考虑设计以及您所针对的所有静态/非静态内容。 - Jcl
我已经编辑了问题,以显示静态构造函数将首先被调用。关于您评论的后半部分,我很快会再次编辑我的问题,并展示我遇到这个问题的实际代码。 - Hele
我强烈建议您更改这个实现设计,因为我看到您的静态成员(c)依赖于实例成员(b),这绝不是一个好主意。 - Jenish Rabadiya
5个回答

9
让我们先定义一下什么是“静态”,以及静态成员和实例成员之间的区别。
静态成员是不需要实例即可存在的成员:它“属于类”,而不是对象(即类的实例)。
现在,“只读”修饰符表示一个成员只能在构造函数中赋值(或在其声明中,但这与本文内容无关)。
有两种类型的构造函数:静态构造函数和实例构造函数……它们之间的区别就是上面所述的区别,并且“只读”修饰符当然适用于每种类型的构造函数:“static readonly”表示“只能在静态构造函数中更改其值”,而实例“readonly”表示“只能在实例构造函数中更改其值”。
静态构造函数在第一次访问该类型时被调用,因此它总是首先被调用。
现在,在你的示例中,你只是随意地将成员更改为“static”或不变,只是为了尝试编译。
好好想想……在“static”上下文中,你根本没有实例,因此无法在“static”构造函数中访问实例成员……而且,当静态构造函数被调用时,你无论如何都无法拥有任何已初始化的实例,即使是外部定义的,因为它将始终在你有机会初始化之前被调用。
因此,在静态构造函数内的这一行是没有意义的:
c = new Apple(b.getDna());

您正在尝试访问实例成员b,但没有说明应该从哪个实例获取该值。

您真的应该重新考虑您的设计,并思考成员是否应该是静态的,而不仅仅是“移动东西并尝试使其编译和运行”。


“只能在构造函数中为成员赋值”的说法是错误的,readonly 还可以在声明时赋值。” - Shaun Luttin
@ShaunLuttin 没错,但我认为这与问题无关:我会编辑并将其添加为评论。 - Jcl
这可能与问题不完全相关,但无论如何:我的真实代码是服务器的代码。我只有一个服务器类的实例,它会生成多个迷你服务器(基本上是哈哈)。任何一个迷你服务器都应该能够在发生重大错误时启动全局关闭。主服务器类具有启动关闭的功能。为了调用它,我可以将关闭函数的副本发送到每个迷你服务器,或者使服务器关闭静态,因为这样更简单。续... - Hele
现在我意识到,让我的整个主服务器变成静态的是有意义的,因为只会存在一个副本。但如果我将主服务器变成实例类,我该如何解决这个问题呢? - Hele
@Hele 这并不适合放在评论中,需要一些示例来考虑正确的设计……有许多方式可以实现你想要的。 - Jcl

1
你正在试图在实例构造函数中为只读静态变量赋值。在调用实例构造函数时,这些变量已经被赋予了一个空值。
public SomeClass()
{
    a = new Apple(); // it is already assigned as null. 
    b = new Orange(a.getDna()); //there is no value assigned to it yet
    c = new Apple(b.getDna()); //it is already assigned as null
}

这是因为在实例构造函数之前调用静态构造函数。你可以在这里找到更多细节:
“静态构造函数会在创建第一个实例或引用任何静态成员之前自动调用以初始化类。”
但是,当你尝试在静态构造函数中访问实例变量时,就会遇到问题。此时,实例构造函数尚未被调用,这意味着变量b尚未初始化。
你面临的是一个非常常见的问题,即尝试混合使用实例变量和静态变量。这种方法可能会导致非常奇怪的行为,就像你现在遇到的一样。
我建议你不要混合使用这些变量,将它们全部设置为静态变量或全部设置为实例变量,但不要混合使用。否则,你可能会在不久的将来面临不同的问题。

1

这里有一个错误的警告吗?

没有。警告是正确的。 如果一个字段是readonly,我们可以在两个地方给它赋值:在声明时或在构造函数中。此外,如果某些东西是static,它的关联构造函数也是static的。因此,我们可以在两个地方为staticreadonly字段赋值:

  1. 在声明时或
  2. static构造函数中。

我们不能在实例构造函数中这样做。

你的另一个问题是static字段无法依赖于实例字段。

我该如何规避这个问题?

这里有一种创造性的方法来规避它:

  1. static构造函数中,将值赋给static _b
  2. 在实例构造函数中,将static _b赋值给实例的b
我们甚至可以在完成后将_b = null赋值,并仍然访问我们早先分配给b的值。
以下是示例:
public class SomeClass
{
    private static readonly Apple a, c;
    private static Orange _b;
    private readonly Orange b;

    static SomeClass()
    {
        a = new Apple();        
        _b = new Orange(a.getDna());
        c = new Apple(_b.getDna());
    }

    public SomeClass()
    {
        b = _b;
        _b = null;
    }

    //
    // the rest is just class definitions
    //      

    public class Apple
    {
        public Apple(object o = null) {}
        public object getDna() { return new object(); }
    }

    public class Orange
    {
        public Orange(object o = null) { }
        public object getDna() { return new object(); }
    }
}

它确实让您规避了这个问题。


1
错误信息实际上是正确的。
首先,静态意味着它属于类。非静态意味着它是每个实例的。实例方法可以修改静态变量,但静态方法不能修改实例变量(它会修改哪个实例?)
鉴于此,只读意味着您只能在创建期间(例如构造函数)进行初始化。
您之所以会出现错误,是因为您正在尝试在实例构造函数中创建只读静态变量之后进行分配。

1

这个错误是正确的,因为如果你创建另一个 SomeClass 的实例,静态字段 a 将会被赋值两次,违反了只读约束。


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