tags:

views:

294

answers:

6

Let's say I have:

public class Fruit
{

    public static List<String> Suppliers { get; protected set; }

    static Fruit()
    {
        Suppliers = new List<String>();
        Suppliers.Add("Company A");
    }

}

public class Banana : Fruit
{

    static Banana()
    {
        Suppliers.Add("Company B");
    }

}

If I just do this in the calling code:

foreach(String supplier in Banana.Suppliers)
    Console.WriteLine(supplier);

I get:

  • Company A

Whereas if I do:

Banana b = new Banana();
foreach(String supplier in Banana.Suppliers)
    Console.WriteLine(supplier);

I get (the desired result):

  • Company A
  • Company B

Edit: After reading the responses I understand that this won't work.

What I want in my production code is a list of values that is common to the type of object and I want to dynamically add different values to that list of strings based on the subtype. (The context is LDAP - all entries have objectClass=top and all user-objects have objectClass=user,top,organizationPerson,person). Guess I have to use an interface or different lists in each subclass or something if no one has a better suggestion?

+6  A: 

The results you are seeing are caused by the way that static constructors work. The CLR does not actually execute the static constructor util the first instance is used, which is why you only get the desired results in your second example. See MSDN for more info.

heavyd
+12  A: 

For one thing, accessing Banana.Suppliers is misleading. It will always yield the same result as accessing Apple.Suppliers etc - you've got a single collection of suppliers.

Basically any time you access Banana.Suppliers the compiler emits a call to Fruit.Suppliers: that's why just calling Banana.Suppliers isn't triggering the static constructor which adds the banana supplier.

The reason you only see the suppliers added in the static constructor for bananas after you've created a banana is that that forces the static constructor to run. You could do anything else that forced the static initializer to run, and you'd get the same results. One example would be calling a static method within Banana itself.

Now, I strongly suspect that you've got a significant problem in that you'll be using the same suppliers for all types. Obviously this isn't your real code, and the best solution will depend on what you want your real code to do. Generics can give you effectively "per type" static variables using type arguments : Foo<Banana>.StaticProperty and Foo<Apple>.StaticProperty will really be different, assuming StaticProperty is declared in Foo<T>.

EDIT: With regards to your edit, I would suggest avoiding using statics here. Possibly create a factory for each type (implementing an interface which may be generic). Note that you may well be able to avoid creating a separate factory type for each subtype, if you can create an appropriate instance with all the relevant items for each type.

We'd really need to see more of an example to say for sure, but in general I find that the less static data you have, the more testable your design will be and the less you'll run into problems like this :)

Jon Skeet
Downvoters: please leave a comment...
Jon Skeet
Please see my edit for an explanation of the production code environment. Makes more sense... Any thoughts on how to implement that?
antirysm
It was me Jon, I misread or maybee you edited but at first I didn't think you mentioned it has to do with him not triggering the static constructor on Bannana. Anyways now its clear so I +1
JoshBerke
@Josh: Righto - thanks for expalining :) (It may well have been an edit.) @antirysm: will look when I'm back home.
Jon Skeet
+2  A: 

The cuase of this is quite easily explainable, in fact. When you get Banana.Suppliers, you're actually just referencing Fruit.Suppliers - the compiler ends up resolving to the Fruit class in this case because of the way inheritance works (nothing is defined in the Banana class. Thus, your static constructor for Banana is not called in the first example because you have technically not yet referenced the class in any way. This is of course why you are missing the "Company B" item in the first result.

The problem here is a design issue. I'm not sure exactly what your intentions are here, but if you do in fact want a property in the Fruit class to store the list of all suppliers, then you need to initialise the list completely within the static constructor for the Fruit class. In general, however, I would think you'd want a separate dataset class or such for this purpose. Static properties is probably not the way to approach this design feature.

Noldorin
+1  A: 

Accessing Banana.Suppliers gets compiled to an access of Fruit.Suppliers ... which means that effectively your code is not actually touching the Banana class, which means that .NET has no reason to execute the static constructor of Banana.

If you did pretty much anything else with the Banana class (such as you creating an instance of it for example), the Banana static constructor runs.

jerryjvl
A: 

A static constructor does not behave like an instance constructor (they're not called explicitly). You have to access a property that's actually on the Banana class before it is constructed. You're trying to apply some principles of object orientation to static behavior on classes. They're not equatable, and doing this is going to lead you down a route will ultimately lead you to despair.

This code:

foreach(String supplier in Banana.Suppliers)
    Console.WriteLine(supplier);

Is the exact equivalent to this code:

foreach(String supplier in Fruit.Suppliers)
    Console.WriteLine(supplier);

So the static constructor on Banana is never called, because it is never needed. The below code demonstrates how making a call to a static member of fruit causes its static constructor to be called, yielding the results you're looking for.

public class Banana : Fruit
{
    static Banana()
    {
        Suppliers.Add("Company B");
    }
    public static void Foo()
    {

    }
}

// ...
Banana.Foo();
foreach (var supplier in Banana.Suppliers)
    Console.WriteLine(supplier);
Michael Meadows
Actually, as Jon Skeet has already pointed out this won't work the way I intended because there's only one static list of fruit. Guess I'm not the only one a bit confused... ;-)
antirysm
Yeah, I took too long to compose my answer. I left it, though, because the example at the bottom demonstrates another way to force the static constructor to be called.
Michael Meadows
A: 

Edit: I'll post a new question instead of building on this...

antirysm