【问题标题】:Is there a way to force static fields to be initialized in C#?有没有办法强制在 C# 中初始化静态字段?
【发布时间】:2010-01-28 13:15:00
【问题描述】:

考虑以下代码:

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();
    }
}

【问题讨论】:

    标签: c# static initialization


    【解决方案1】:

    您的问题的答案是“嗯,是的”。但是“强迫”它的两种方式之一是你已经在做的事情。

    语言规范中的相关部分是Static constructors,具体来说:

    类的静态构造函数在给定的时间里最多执行一次 应用领域。触发静态构造函数的执行 由以下事件中的第一个在应用程序中发生 域名:

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

    如果一个类包含在其中执行的 Main 方法(第 3.1 节) 开始时,该类的静态构造函数在 Main 之前执行 方法被调用。如果一个类包含任何静态字段 初始化程序,这些初始化程序按文本顺序执行 在执行静态构造函数之前。

    【讨论】:

    • 是的!我也在规范中阅读过这个。但我希望有另一种非官方的方式来做到这一点。谢谢!
    • 如果有非官方的方式,我很想听听!但也许人们应该回避这种技术。 Unofficial.Equals(Hack)?
    • @ChristofferLette 对于 Rafael 的情况,实际上在 MSDN 中出现了另一种方式,可以追溯到 .Net 1.1,System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(..)。有关 Rafael 示例的工作变体,请参阅我的答案。
    【解决方案2】:

    在这种情况下,实际上有一种方法可以强制初始化属性。更改需要向基类添加一个类型参数,以表示将包含要初始化的字段的基类的未来子类。然后我们可以使用 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

    【讨论】:

    • 先生,您救了我的命。来自我的 +1。
    • @S.TarıkÇetin 很高兴听到这对您有所帮助!
    • 太棒了,它非常接近我真正需要的东西!唯一最重要的是如果超类也可以派生并且新字段也将被初始化。像这样:dotnetfiddle.net/RGBghM 我知道的唯一方法是制作我不想要的Superclass,因为这是我的默认实现,SuperClassCustom 是针对特定客户的一些特定自定义。
    • @Wolfsblvt 是的,遗憾的是它不会选择那个子类。但是,如果 SuperClassCustom 仅提供替代功能,并且应将其视为可以在 Superclass 上枚举的整个集合的一部分,那么这可能对您有用:dotnetfiddle.net/LW3D8u。我只是将 SuperClassCustom 的静态实例移动到超类“枚举”。
    • 是的,但问题是 SuperClassCustom 确实提供了新的功能,所以新的属性和东西。当我们想要自定义一些东西时,我们不能触及“核心”文件。这才是真正的问题。虽然找到了一个可行的解决方案。不是很好,但它可以完成工作。如果它们派生自 TSubclass,我将遍历所有程序集和类型一次以调用所有静态构造函数:dotnetfiddle.net/C0BYCM
    【解决方案3】:

    但您从不设置属性 - 而是直接设置支持字段,因此在创建 program1、program2 和 program3 时不会通过逻辑添加到静态列表。

    即你需要改变:

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

    到:

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

    【讨论】:

    • 这就是为什么我们不对成员和属性名使用相同的变量名!
    • 当然,您的代码允许您多次将同一个实例添加到列表中,因此您可能希望将“添加到静态列表”逻辑移到构造函数中,或者只是检查是否它已经在列表中...
    • 好的!我的样品坏了。我在 value 字段中输入了错误的 Value 属性。但遗憾的是,这不是我的实际代码中发生的事情。我将使用构造函数将实例添加到值列表中,将示例替换为另一个更像我的实际代码的示例。我认为这与 John Skeet 在这里描述的内容有关:msmvps.com/blogs/jon_skeet/archive/2010/01/26/… 无论如何谢谢!
    • @Travis “这就是为什么我们不为成员和属性名称使用相同的变量名!” - 实际上,您可以使用相同的名称,dit 是 C# 中非常常见的样式,但您通常会将支持字段设为私有,并且仅将属性公开为公共。
    • @Rafael:所以当你犯了一个微妙的错字时,你更喜欢有一个不是编译时错误的逻辑错误?有趣...
    【解决方案4】:

    实际上看起来你拼错了 'value' -> 'Value' 所以:

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

    pretty 打印更多行

    【讨论】:

      【解决方案5】:

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

      C# 语法允许Superclass.Values,但最终编译的方法调用将是Subclass.Values getter。所以Superclass 类型实际上从未被触及。另一方面,Superclass.SuperclassA1 确实接触了类型并触发了静态初始化。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-15
        • 1970-01-01
        • 1970-01-01
        • 2016-01-05
        • 1970-01-01
        相关资源
        最近更新 更多