当访问基类成员时,子静态构造函数未被调用

11

我有一个定义如下的类:

public class DatabaseEntity<T> where T : DatabaseEntity<T> {
    public static string Query { get; protected set; }
    public static IList<T> Load() {
        return Database.Get(Query);
    }
}

public class Node : DatabaseEntity<Node> {
    static Node() {
        Node.Query = @"SELECT Id FROM Node";
    }
}

当我在代码后台(Window.xaml.cs)中运行Node.Load()时,节点的静态构造函数从未被触发;或者至少没有命中断点,并且未将Node.Query设置为除null以外的任何值。
是否有任何原因会出现这种情况?
解决方案
请参阅下面的答案,了解一些解决方案。对于我的情况,我决定简单地使Query变量公共,并在一个地方设置所有Query实例。(不是理想的解决方案,但它有效。)

它是通过DataContractSerializer进行反序列化的吗?据我记得,DataContractSerializer不会触发构造函数。 - PhillipH
为什么不直接把查询 private const string Query = [query]; 设为常量呢? - atlaste
@CharlesW 继承静态的?这种东西不存在,只有隐藏,即在这种情况下它不被调用。 - atlaste
我已经编辑了帖子,以反映出"继承"的内容。 - Charles W
2
@RicoSuter 如果“T”不同,则查询将不同。 DatabaseEntity <Node> .Query!= DatabaseEntity <SubNode> .Query - Charles W
显示剩余4条评论
3个回答

5
问题在于您对静态构造函数何时被调用的假设。这份不是很清晰的文档说明:

它会在创建第一个实例或引用任何静态成员之前自动调用。

如果您调用了

Node.Load();

你正在调用 Node 类的静态方法,但实际上你是在基类上调用它,因为这是它的实现位置。
所以,要解决这个问题,你有两个选择。第一,你可以在调用 Load() 之前创建一个 Node 类的新实例来显式触发静态构造函数。
var foo = new Node(); // static ctor triggered
Node.Load();

或者创建一个受保护的虚成员,以便基类可以调用该成员获取查询值(不幸的是,这里不能使用抽象)。
public class DatabaseEntity<T> where T : Derp {
    protected abstract string Query { get; }
    public static IList<T> Load() {        
        return Database.Get(new DatabaseEntity<T>().Query);
    }
}

这两种方法都有些不规范。最好完全放弃静态方法,改用实例方法。静态方法应该谨慎使用,因为它们会导致紧密耦合和其他设计上的问题,比如这个问题。


4
是的,只有在访问类的成员或创建第一个实例时才会调用静态构造函数。
在您的情况下,您正在访问 DatabaseEntity<T>.Load,因此将调用 DatabaseEntity<T> 的静态构造函数,而不是它派生类的静态构造函数。
即使您调用 Node.Load,它在编译时也被映射到 DatabaseEntity<Node>。因此,在技术上,您根本没有访问 Node 类。

这是正确的,您可以通过在调用Node.Load()之前创建节点类的实例来演示此行为,然后它应该可以工作。如果在调用之前没有创建实例,则不会起作用。 - ramsey_tm
这是一个很好的解释。除了显而易见的 Node n = new Node();,你能提供一些解决这个问题的提示或建议吗? - Charles W
2
@CharlesW 我怀疑这是一个设计问题。如果我不知道你想要实现什么,我无法帮助你解决这个问题,抱歉。显而易见的解决方法是添加一个带有新修饰符的 Node.Load 方法,它只是调用基类的实现,但是我不建议这样做。 - Sriram Sakthivel

1
您还可以直接使用System.Runtime.CompilerServicesRuntimeHelpers类型调用类构造函数,方法如下: RuntimeHelpers.RunClassConstructor(type.TypeHandle); 例如,您可以使用反射循环遍历继承链中的所有类型并调用每个静态构造函数。

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