在C#中是否有一种方法可以强制静态字段进行初始化?

10

考虑以下代码:

class Program
{
    static Program() {
        Program.program1.Value = 5;
    }

    static List<Program> values = new List<Program>();
    int value;
    int Value
    {
        get { return value; }
        set { 
            this.value = value;
            Program.values.Add(this);
        }
    }

    static Program program1 = new Program { value = 1 };
    static Program program2 = new Program { value = 2 };
    static Program program3 = new Program { value = 3 };

    static void Main(string[] args)
    {
        if (Program.values.Count == 0) Console.WriteLine("Empty");
        foreach (var value in Program.values)
            Console.WriteLine(value.Value);
        Console.ReadKey();
    }
}

它只打印数字5,如果删除静态构造函数中的代码,则会打印“Empty”。

是否有一种方法可以强制初始化静态字段,即使尚未使用?

我需要一个名为Values的静态属性,返回引用类型的所有实例。

我尝试了一些此代码的变化,对于某些类型有效,但对于其他类型无效。

编辑:上面的示例已损坏,请尝试这个:

class Subclass<T> {
    static Subclass()
    {
        Values = new List<Subclass<T>>();
    }
    public Subclass()
    {
        if (!Values.Any(i => i.Value.Equals(this.Value)))
        {
            Values.Add(this);
        } 
    }

    public T Value { get; set; }

    public static List<Subclass<T>> Values { get; private set; }
}

class Superclass : Subclass<int>
{
    public static Superclass SuperclassA1 = new Superclass { Value = 1 };
    public static Superclass SuperclassA2 = new Superclass { Value = 2 };
    public static Superclass SuperclassA3 = new Superclass { Value = 3 };
    public static Superclass SuperclassA4 = new Superclass { Value = 4 }; 
}

class Program
{
    static void Main(string[] args)
    {
        //Console.WriteLine(Superclass.SuperclassA1); //UNCOMMENT THIS LINE AND IT WORKS
        foreach (var value in Superclass.Values)
        {
            Console.WriteLine(value.Value);
        }
        Console.ReadKey();
    }
}
5个回答

11

实际上在这种情况下有一种强制初始化属性的方法。需要向基类添加一个类型参数,代表将包含要初始化字段的基类的未来子类。然后我们可以使用RuntimeHelpers.RunClassConstructor,以确保子类静态字段已被初始化。

以下内容将会产生您所期望的结果:

class Subclass<TSubclass, T> 
{
    static Subclass()
    {
        Values = new List<Subclass<TSubclass, T>>();
        // This line is where the magic happens
        System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(TSubclass).TypeHandle);
    }
    public Subclass()
    {
        if (!Values.Any(i => i.Value.Equals(this.Value)))
        {
            Values.Add(this);
        } 
    }

    public T Value { get; set; }

    public static List<Subclass<TSubclass, T>> Values { get; private set; }
}

class Superclass : Subclass<Superclass, int>
{
    public static Superclass SuperclassA1 = new Superclass { Value = 1 };
    public static Superclass SuperclassA2 = new Superclass { Value = 2 };
    public static Superclass SuperclassA3 = new Superclass { Value = 3 };
    public static Superclass SuperclassA4 = new Superclass { Value = 4 }; 
}

public class Program
{
    public static void Main()
    {
        foreach (var value in Superclass.Values)
        {
            Console.WriteLine(value.Value);
        }
        Console.ReadKey();
    }
}

发生的事情是,对 RuntimeHelpers.RunClassConstructor(typeof(TSubclass).TypeHandle) 的调用将强制执行 TSubclass 的静态构造函数,如果它尚未运行。这确保了静态字段按照以下来自https://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx的顺序进行初始化:

如果类包含任何具有初始化程序的静态字段,则在执行静态构造函数之前,这些初始化程序会按照文本顺序执行。

以下是演示其工作原理的 dotnetfiddle: https://dotnetfiddle.net/MfXzFd

您拯救了我的一天,先生。我给您点赞。 - starikcetin
@S.TarıkÇetin 很高兴听到这对你有所帮助! - Tyree Jackson
太棒了,这非常接近我实际需要的!唯一更好的是如果还可以派生超类并初始化新字段。就像这样:https://dotnetfiddle.net/RGBghM我知道的唯一方法是制作“Superclass”,但我不想这样做,因为这是我的默认实现,“SuperClassCustom”是针对特定客户的某些特定定制。 - Wolfsblvt
@Wolfsblvt 是的,遗憾的是它不会捕捉到那个子类。然而,如果 SuperClassCustom 仅提供替代功能,并且应该被视为可以在 Superclass 上枚举的整个集合的一部分,那么这可能适合您:https://dotnetfiddle.net/LW3D8u。我只是将 SuperClassCustom 的静态实例移动到了 Superclass "enum" 中。 - Tyree Jackson
2
是的,但问题在于SuperClassCustom确实提供了新功能,所以有新的属性等等。当我们想要自定义某些东西时,我们不能触及“核心”文件。这才是真正的问题。不过我找到了一个可行的解决方案。虽然不太好看,但它能完成任务。我正在迭代所有程序集和类型一次,以调用所有静态构造函数,如果它们派生自TSubclass:https://dotnetfiddle.net/C0BYCM - Wolfsblvt

9
你的问题的答案是“是的”。但是,其中一种“强制”它的方法就是你已经在做的事情。
相关的语言规范在静态构造函数中,具体来说:

类的静态构造函数在给定应用程序域内最多执行一次。触发静态构造函数的执行的事件是以下事件中最先发生的一个:

  • 创建类的实例。
  • 引用了类的任何静态成员。

如果一个类包含Main方法(第3.1节),则该类的静态构造函数会在调用Main方法之前执行。如果一个类包含任何带有初始化器的静态字段,则这些初始化器会在执行静态构造函数之前按文本顺序立即执行。


是的!我在规范文档中也读到了这一点。但我还抱有希望,认为可能有另外一种非官方的方法来实现它。谢谢! - Rafael Romão
如果有非官方的方法,我想听听!但也许我们应该避免使用这样的技术。“非官方”.Equals(黑客)? - Christoffer Lette
@ChristofferLette 对于 Rafael 的情况,实际上 MSDN 中还有另一种方法,可以追溯到 .Net 1.1,即 System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(..)。请参见我的答案,其中包含 Rafael 示例的可行变体。 - Tyree Jackson

5

但你从未设置该属性--相反,你直接设置了后备字段,因此没有通过逻辑添加到静态列表中,当创建program1、program2和program3时。

也就是说,你需要更改:

    static Program program1 = new Program { value = 1 };
    static Program program2 = new Program { value = 2 };
    static Program program3 = new Program { value = 3 };

to:

    static Program program1 = new Program { Value = 1 };
    static Program program2 = new Program { Value = 2 };
    static Program program3 = new Program { Value = 3 };

1
这就是为什么我们不会在成员和属性名称中使用相同的变量名! - Travis Gockel
2
@ Travis:“这就是为什么我们不会在成员变量和属性名中使用相同的变量名!” - 实际上,在C#中你可以使用相同的名称,这是一种非常常见的风格,但通常你会将后备字段设置为私有,并且只公开属性。 - Steve
1
@Travis 和 Steve:我同意 Steve 的观点。我真的不喜欢另一个下划线的模式。 - Rafael Romão
1
@Rafael:所以你更喜欢在你犯了一个微小的拼写错误时出现逻辑错误,而不是编译时错误?有意思... - Travis Gockel
对于在Rafael的评论中遇到Jon Skeet博客链接失效的任何人,新的有效链接是https://codeblog.jonskeet.uk/2010/01/26/type-initialization-changes-in-net-4-0/。 - AnorZaken
显示剩余3条评论

2
实际上,你拼错了'value',应该是'Value'。因此:
    static Program program1 = new Program { Value = 1 };
    static Program program2 = new Program { Value = 2 };
    static Program program3 = new Program { Value = 3 };

美化打印更多行


0

第二个示例不起作用,因为ValueSubclass的静态成员。

C#语法允许Superclass.Values,但最终编译的方法调用将是Subclass.Values getter。因此,类型Superclass实际上从未被触及。Superclass.SuperclassA1则会触及该类型并触发静态初始化。

这就是为什么C#实际上没有隐式静态初始化,您需要像MEF和Unity这样的组合框架。


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