一般来说,我的理解是,类中的静态对象是在第一次引用类时构造的.然而,我正在经历一种我没有预料到的行为.

请考虑下面的类

public abstract class SmartColor<TColor> where TColor : SmartColor<TColor> {
    private static readonly ConcurrentDictionary<string, TColor> _items = new();

    protected SmartColor(string code, string name) {
        Code = code;
        Name = name;
        //register all colors
        foreach (var field in typeof(TColor).GetFields(BindingFlags.Public | BindingFlags.Static)) {
            TColor? item = (TColor?)field.GetValue(null);
            if (item is not null) {
                Register(item);
            }
        }
    }

    public string Code { get; }

    public string Name { get; }

    public static TColor FromCode(string code) {
        if (_items.TryGetValue(code, out var result)) {
            return result;
        }
        return null;
    }

    private void Register(TColor item) {
        _items.GetOrAdd(item.Code, item);
    }
}

它又是从这个继承而来的

public class WebColor : SmartColor<WebColor> {
    public static readonly WebColor White = new WebColor("#ffffff", nameof(White));
    public static readonly WebColor Black = new WebColor("#000000", nameof(Black));

    protected WebColor(string code, string name) : base(code, name) {
    }
}

如果按如下方式使用,它将生成NullReferenceException

public static void Main()
{
    WebColor color = WebColor.FromCode("#ffffff");
    Console.WriteLine(color.Name);
}

我能理解为什么会发生这种事吗?小提琴here

推荐答案

the documentation for Static Constructors人起:

静态构造函数用于初始化任何静态数据,或 执行只需执行一次的特定操作.它 在创建第一个实例之前自动调用,或者任何 静态成员被引用.

所以让我们来详细地看看这个.要使代码正常工作,必须在调用WebColor.FromCode("#ffffff")之前调用类WebColor的静态字段构造函数.

是否创建了WebColor个实例?号

它的任何静态成员是否被引用?不是的.FromCode()方法notWebColor的成员-它是SmartColor<TColor>的静态成员,虽然基类的静态方法可以通过派生类调用,但它们实际上不是该派生类的成员.

因此,调用WebColor的静态初始化器的标准尚未满足,因此White初始化没有发生,因此它没有被添加到SmartColor<T>中的_items.

但请注意,由于对WebColor.FromCode()的调用,_items本身已被初始化.


您可以通过将静态构造函数添加到SmartColor<TColor>来以一种略显老套的方式修复此问题,如下所示:

static SmartColor()
{
    Type type = typeof(WebColor);
    System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);
}

表达了这样一个事实,即SmartColor<TColor>的静态成员对WebColor类型具有时间依赖性,并且当SmartColor<TColor>个静态初始化器运行时,它将强制静态WebColor初始化器运行.

然而,正如其他人指出的那样,这是一个有点笨拙的设计,因此可能值得采用不同的方法!


附录:

您可以通过使用如下类来避免使用静态构造函数:

public sealed class StaticClassInitialiser<T>
{
    public StaticClassInitialiser()
    {
        System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle);
    }
}

然后在SmartColor<TColor>类中创建该类的静态实例:

static readonly StaticClassInitialiser<WebColor> _ = new();

这可能会愚弄SonarQube,但这算不上什么改进--我把它写在这里只是出于好奇.

Csharp相关问答推荐

ASP.NET Core:如何在IPageFilter中注入ApplicationDbContext

以自动方式注销Azure身份应用程序

如何使用CsvReader获取给定列索引的列标题?

. NET Core DB vs JSON模型设计

如何将Kafka消息时间戳转换为C#中的日期和时间格式?

MAUI查询参数单一字符串项将不起作用

选取器与.NET Maui MVVM的绑定属性

如何使用C#Interop EXCEL创建度量衡

使页面内容居中

JsonSerializer.Deserialize<;TValue>;(String,JsonSerializerOptions)何时返回空?

MS Graph v5.42.0:在寻呼消息时更改页面大小

为什么在使用动态obj+类obj时会调用串联?

C#阻塞调用或await calling inside calling方法

如何将此方法参数化并使其更灵活?

将字节转换为 struct 并返回

在被Interactive Server切换后,Blazor SSR页面无法正确加载JS

两个DateTimeOffset之间的差异返回意外的负值

VS代码扩展无法在新版本扩展C#中运行从v2.10.28开始

使用Try-Catch-Finally为API端点处理代码--有什么缺点?

缩写的MonthNames有问题