How to trigger a static constructor
Asked Answered
G

5

6

code:

class Base<T,U> where T:Base<T,U>,new()  where U :class
{
    protected static U _val = null;
    internal static void ShowValue()
    {
        if(_val == null)new T(); //Without this line, it won't work as expected
        Console.WriteLine (_val);
    }
    internal static void Virtual()
    {
        Console.WriteLine ("Base");
    }
}
class Deriv :Base<Deriv,string>
{
    static Deriv()
    {
        _val = "some string value";
    }
    internal static new void Virtual ()
    {
        Console.WriteLine ("Deriv");
    }
}
 public static void Main (string[] args)
{
    Deriv.ShowValue();            
    Deriv.Virtual();
}

Thanks to the generics of .NET, I can create a bunch of specific classes reusing generic static methods defined in the generic base class. It can mimic inheritance polymorphism to some extent. But in order to initialize different version of static fields, I've to use static constructors. Unfortunately, we can't call them directly, therefore, we have to figure out a way to trigger it's invocation. The example given above showed a way. But I don't like either the instantiation,or the reflection approach. We also can't make a constraint on a static method of a generic parameter. So, I'd like to ask, if there is another way to do this kind of job!

Thanks beforehand!

~~~~~~~~~~~~~~~~

Some Conclusion (Maybe a little early):

It seems there is no workaround to deal with this kind of situation. I have to instantiate a subclass or use reflection. Considering the .cctors need merely be called once, I'm in favor of the reflection approach, because in some case, a new() constraint is just not a choice - like you're not supposed to expose the parameterless ctor to user.

After conducting further experiment, I find out that the .cctors may be called multi-times but only the first invocation will affect the setting of static fields. That's weird, but a good weirdness!

    class MyClass 
    {
        static int _val = 0;
        static MyClass()
        {
            _val++;
            Console.WriteLine (_val);
        }
    }
    public static void Main (string[] args)
    {
        ConstructorInfo ci = typeof(MyClass).TypeInitializer;
        ci.Invoke(new object[0]);
        ci.Invoke(new object[0]);
        ci.Invoke(new object[0]);
    }
//result:
//1
//1
//1
//1
Gallicize answered 2/8, 2011 at 6:15 Comment(1)
PS:I know it can't be called directly, and I know the instantiation and method reference approach. I'm just looking for a workaround. As the given example showed, it's only a generic parameter, its static methods can't be referenced. I'd like to know apart from the instantiation way is there another way, no instance involved.Gallicize
B
6

I would strongly advise you to rethink your design. Attempting to use this sort of workaround for "static inheritance" is fighting against some of the core designs of .NET.

It's unclear what bigger problem you're trying to solve, but trying "clever" code like this to simulate inheritance will lead to code which is very hard to maintain and diagnose in the longer term.

Without actually using a member of Deriv (or creating an instance of it), you basically won't trigger the static constructor. It's important to understand that Deriv.ShowValue() is basically converted into a call to

Base<Deriv, string>.ShowValue();

... so you're not actually calling anything on Deriv. Your calling code would actually be clearer if it were written that way.

EDIT: One other (clearly unfortunate) reason to avoid using type initializers explicitly is that there's a bug in .NET 4.5 which causes an exception to be thrown inappropriately in some cases. See my question on the topic for more information.

Bara answered 2/8, 2011 at 6:30 Comment(17)
:I just wanna write less code. In c++ I can do this sort of thing just fine. My design is kinda like a object pool,with no user side instantiation. Use this approach I like implement pool management all in base class with help of generic parameter.Gallicize
@Need4Steed: Don't try to treat C# like C++. They're different languages, with different idioms - and generics are very different from templates, under the hood. If you want polymorphism, you should be looking at creating instances instead, even if you only create one instance of each class.Bara
It's already to big to change, I have to stick with it. Besides, I still think my design needs less code than the alternatives.Gallicize
@Need4Steed: Then I'm afraid I can't help you. If you fight against the .NET type system like this you are going to get burned. Good luck, but I think you're making a mistake by not revisiting this decision. Unless this is a really temporary tool, remember that your code is likely to spend much longer being maintained than being originally written...Bara
Don't scare me, I'm not that combustible :) I just think, if the language provided some cool feature, with which you can twist things, then what keeps you from using it? I have a bunch of sibling classes they all supposed to keep their own object pools as static fields, how can I create such kind of things in another way, yet with no repeat of code? Any pragmatic suggestions? I'm all ears.Gallicize
@Need4Steed: There are many evil things you can do with C#, and it's fun to know about them - but not necessarily to use them in production code. My suggestion is that you move away from the idea of "all supposed to keep their own object pools as static fields". Could you perhaps have a dictionary from a type to the pool of that type? It's unclear what you're really doing, but all I can say is that trying to combine statics and polymorphism has rarely ended well in my experience.Bara
Many thanks for your willing advices, I will reconsider my design, and may refactor it to make it DECENTER at some point. ^_^ It's just a personal pet project. I'm trying to work it through to collect some experience in c#, and primarily to have fun. Regards!Gallicize
Jon, I respect your insight into C# a lot, but in this case I think you’re absolutely wrong. I came up with the OP’s construct independently and for a very similar purpose. This was after three hard thinks about the code architecture. I don’t think you can meaningfully accuse us of having come up with a bad design. The fact that we need to “fight against some of the core designs of .NET”, in this case, indicates that .NET needs better support for this scenario in which we seem to need static virtual members. (Although, to be fair, in my case, a new(ParamTypes) generic constraint would do.)Tyro
@Timwi: Given that the OP is fighting one of the core designs of .NET, rethinking the design still seems like good advice to me. Wishing for the presence of static virtual members doesn't make them available to you, and your design should be based on what you can do rather than what you'd like to be able to do.Bara
@JonSkeet: His (and my) design is based on what we can do, because this workaround does effectively make static virtual members available to us. You’re moving goalposts. Please decide on what your position is: that we should make use of what we can do to implement our desired design, or that we should refrain from using things we can do because they constitute “fighing the design of .NET”. And if the latter, please provide an alternative design that doesn’t “fight the design of .NET” but allows us to write our code.Tyro
@Timwi: I'm not moving the goalposts. You clearly are fighting the design of .NET if you have to call a type initializer explicitly. Whenever you have to use a workaround because your design isn't catered for within the type system, you should attempt to redesign. It's impossible to give an alternative design without knowing more about the individual situation - I suspect that anything I'd propose would be shot down due to constraints I'm not aware of.Bara
@JonSkeet: Right, in that case you are just arrogant if you think we are unable to fully think our design through and genuinely come to the conclusion that within the limitations of C#/.NET this design is the least hacky/most maintainable solution that fulfills our requirements.Tyro
@Timwi: Whereas you're arrogant enough to think that no-one could ever come up with anything better? I think we're going to have to agree to disagree. Of course one sometimes has to live with workarounds - but in the question I originally answered there was no indication that the OP had even considered alternative designs. Maybe in your specific case it really is the best approach - but you appear to refuse to entertain the idea that it might not be.Bara
@JonSkeet: Like I said, I reconsidered the design three times and then homed in on this one after discarding two alternatives that were even worse. You appear to refuse to accept this; you think that only someone who hasn’t thought about this at all could come up with this design. Also, I specifically asked you if you knew anything better, so I really don’t see where I’m being arrogant here.Tyro
@Timwi: You've assumed that those three designs are the best ones available. Is there the slightest possibility that there might be an approach you haven't considered? I'm saying there may be something else. The fact that you reconsidered the design shows that you've already effectively gone along with my advice of rethinking the design. If you end up with this being the best of a bad set of options, then so be it - but rethinking was still the right thing to do. As I said before, I can't give you "anything better" without knowing exactly what you're trying to achieve.Bara
@JonSkeet: Well, I was never a fan of long and dragged-out discussions, but ... here’s what I was working on, it’s in a usable state now and you can take a look. Check out the property Constructor in the type GenerexBase, look at how it’s called in various places, and see if you can come up with a better idea. I’d be more than glad to hear one :)Tyro
@Timwi: Okay - I'll have a look over the weekend. In the mean time, check your code against the .NET 4.5 bug :)Bara
T
4

The correct solution is to invoke the type initializer (= static constructor) like this:

typeof(T).TypeInitializer.Invoke(null, null);

It needs both nulls. Specifying only one gives a MemberAccessException.

Thus, your code might want to look something like this:

internal static void ShowValue()
{
    if (_val == null)
    {
        if (typeof(T).TypeInitializer != null)
            typeof(T).TypeInitializer.Invoke(null, null);
        if (_val == null)
            throw new InvalidOperationException(string.Format("The type initializer of {0} did not initialize the _val field.", typeof(T)));
    }
    Console.WriteLine(_val);
}

And with that, you can remove the new() constraint.

Tyro answered 16/11, 2012 at 14:39 Comment(1)
Genuinely not trying to be snarky, but just in case this problem hits your code base later - there's a bug in .NET 4.5 which can cause problems: #11594115. It may not affect your code (there could be any number of reasons) but it's probably worth checking if you're not already on 4.5. (This is an entirely separate matter from whether or not it's generally a good design - it's not like I'm claiming this is karma or anything ;)Bara
H
2

You have no control of when the static constuctor will execute, but what is guaranteed is that it will run before accessing any static property or method and before instantiation.

There is really no reason to want the static constructor to execute at an earlier point. If you are not using anything from the class but you want the code in the static constructor to run, then something is wrong in your design.

Hacienda answered 2/8, 2011 at 6:21 Comment(1)
Old response I know, but its more subtle than this. If a class has a static variable which calls a static function within the class when it is created (static Thing myThing = CreateThingWithStatic();), that code will get called before the static constructor.Willable
S
0

Static constructors are automatically, only once. You cannot call them yourself.

An example from here:

public class Bus
{
    // Static constructor:
    static Bus()
    {
        System.Console.WriteLine("The static constructor invoked.");
    }    

    public static void Drive()
    {
        System.Console.WriteLine("The Drive method invoked.");
    }
}

class TestBus
{
    static void Main()
    {
        Bus.Drive();
    }
}

output:

The static constructor invoked.

The Drive method invoked.
Synn answered 2/8, 2011 at 6:19 Comment(0)
U
0

I direct you to the MSDN article on Static Constructors and about 10% down the page:

A static constructor is called automatically to initialize the class before the first instance is created or any static members are referenced.

Upsurge answered 2/8, 2011 at 6:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.