简体   繁体   中英

Is there a way to force static fields to be initialized in C#?

Consider the following code:

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

It prints only the number 5, and if removed the code in the static constructor, it prints "Empty".

Is there a way to force static fields to be initialized even whether not used yet?

I need to have a static property named Values with returns all instances of the referred type.

I tried some variations of this code and some works for some types but doesn't for others.

EDIT: THE SAMPLE ABOVE IS BROKEN, TRY THIS ONE:

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

The answer to your question is 'well, yes'. But one of the two ways of "forcing" it is what you're already doing.

The relevant section in the language spec is Static constructors , and specifically:

The static constructor for a class executes at most once in a given application domain. The execution of a static constructor is triggered by the first of the following events to occur within an application domain:

  • An instance of the class is created.
  • Any of the static members of the class are referenced.

If a class contains the Main method (Section 3.1) in which execution begins, the static constructor for that class executes before the Main method is called. If a class contains any static fields with initializers, those initializers are executed in textual order immediately prior to executing the static constructor.

There is actually a way to force the initialization of the properties in this case. The change requires adding a type parameter to the base class to represent the future subclass of the base class that will contain the fields to be initialized. Then we can use the RuntimeHelpers.RunClassConstructor to ensure that the sub class static fields are initialized.

The following will yield the results that you are looking for:

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

What is happening is, the call to RuntimeHelpers.RunClassConstructor(typeof(TSubclass).TypeHandle) forces the static constructor of TSubclass to execute if it has not already run. This ensures that the static fields have initialized first as per this line from https://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx :

If a class contains any static fields with initializers, those initializers are executed in textual order immediately prior to executing the static constructor.

Here is a dotnetfiddle demonstrating it working:

https://dotnetfiddle.net/MfXzFd

But you're never setting the property -- instead you're setting the backing field directly, so not going through your logic to add to the static list when creating program1, program2 and program3.

ie you need to change:

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

Actually looks you misspelled 'value' -> 'Value' So:

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

pretty prints more lines

The second sample doesn't work simply because Value is a static member of Subclass .

C# syntax allows Superclass.Values , but eventually the compiled method call will be to the Subclass.Values getter. So the type Superclass is never actually touched. Superclass.SuperclassA1 on the other hand does touch the type and triggers the static initialization.

This is why C# doesn't really have implicit static initialization and you need composition frameworks like MEF and Unity.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM