Are static members of a generic class tied to the specific instance?
Asked Answered
H

6

103

This is more of a documentation than a real question. This does not seem to have been addressed on SO yet (unless I missed it), so here goes:

Imagine a generic class that contains a static member:

class Foo<T> {
    public static int member;
}

Is there a new instance of the member for each specific class, or is there only a single instance for all Foo-type classes?

It can easily be verified by code like this:

Foo<int>.member = 1;
Foo<string>.member = 2;
Console.WriteLine (Foo<int>.member);

What is the result, and where is this behavior documented?

Hardden answered 14/6, 2010 at 12:34 Comment(1)
Short answer: There is a new instance for each actual class, i.e. one for each type of T used (Foo<int> and Foo<string> represent two different classes, and will have one instance each, but several intances of Foo<int> will share a single instance of member). For a more detailed example, see: https://mcmap.net/q/67908/-resharper-warns-quot-static-field-in-generic-type-quotKauai
M
107

A static field is shared across all instances of the same type. Foo<int> and Foo<string> are two different types. This can be proven by the following line of code:

// this prints "False"
Console.WriteLine(typeof(Foo<int>) == typeof(Foo<string>));

As for where this is documented, the following is found in section 1.6.5 Fields of the C# Language Specification (for C# 3):

A static field identifies exactly one storage location. No matter how many instances of a class are created, there is only ever one copy of a static field.

As stated before; Foo<int> and Foo<string> are not the same class; they are two different classes constructed from the same generic class. How this happens is outlined in section 4.4 of the above mentioned document:

A generic type declaration, by itself, denotes an unbound generic type that is used as a “blueprint” to form many different types, by way of applying type arguments.

Marplot answered 14/6, 2010 at 12:42 Comment(11)
Here's a gotcha if you develop in both C# and Java. While Foo<int> and Foo<String> are different types in C#, they are the same type in Java because of how Java deals with generics (type erasure/compiler tricks).Johnnajohnnie
What if this was the case: Foo<int> foo1 = new Foo<int>(); foo1.member=10; Foo<int> foo2 = new Foo<int>(); foo2.member = 20; What happens here?Hautboy
@Hautboy the value of member will be changed for both instances (and will be 20) because they share the same type Foo<int>.Axes
What if you inherit a non-generic base class that has a static member into a derived generic class? Will this still be true?Boyette
@StasIvanov, foo1.member will remain 10 and foo2.member will be 20. Please verifyStaggs
@Staggs you're right. It happens because in this case it's an instance member, not a static one.Axes
@Hautboy if still interested please see my comment above. Answering your question for the first time I didn't quite get the fact that you are accessing instance member, not a static one.Axes
@StasIvanov I don't quite understand: there are no instance members in the code sample above, only static ones. If you whether you access the static member through the type or through an instance of the type makes no difference. In fact, foo1.member would be highlighted by tool such as Resharper, since it's somewhat misleading: it looks like an instance member access, but it is a static member access.Mo
@FredrikMörk I've just tried to access a static member using an instance reference and I see CS0176 error saying that I should prefix this call by a type name instead.Axes
@StasIvanov i believe you work mainly in C++? There you're allowed to make calls to static members using instances. Anyway, i think it's probably best to describe the situation in terms of fully qualified names. If there are two static members with the same fully qualified name they point to the same value, otherwise they don't.Hautboy
Yeah interesting. When using nested classes or static methods in Java you do not need to declare the generic params of the enclosing class, but in C# you do (even though they don't have any use - I just declare object every time). So much for C# generics being superior to Java's (they still are but, this is an unfortunate downside).Soares
B
19

The problem here is actually the fact that "generic classes" are not classes at all.

Generic class definitions are just templates for classes, and until their type parameters are specified, they are just a piece of text (or a handful of bytes).

At runtime, one can specify a type parameter for the template, thus bringing it to life, and creating a class of the, now, fully specified type. That's why static properties are not template-wide, and that's why you cannot cast between List<string> and List<int>.

That relationship kinda mirrors the class-object relationship. Just like classes do not exist* until you instantiate an object from them, generic classes do not exist, until you make a class based on the template.

P.S. It's quite possible to declare

class Foo<T> {
    public static T Member;
}

From this is kinda obvious that the static members cannot be shared, as T is different for different specializations.

Betthezel answered 14/6, 2010 at 13:46 Comment(0)
M
4

They are not shared. Not sure where it's documented but analysis warning CA1000 (Do not declare static members on generic types) warns against just this due to the risk of making the code more complicated.

Morbidity answered 14/6, 2010 at 12:37 Comment(2)
Wow, yet another rule I wouldn't get along with on FX Cop.Until
Its documented hereCondensed
D
4

C# implementation of generics is more closer to C++. In both of these languages MyClass<Foo> and MyClass<Bar> don't share static members but in Java they do. In C# and C++ MyClass<Foo> internally creates entirely new type at compile time as if generics are kind of macros. You can usually see their generated names in stack trace, like MyClass'1 and MyClass'2. This is why they don't share static variables. In Java, generics are implemented by more simpler method of compiler generating code using non-generic types and adding type casts all over. So MyClass<Foo> and MyClass<Bar> don't generate two entirely new class in Java, instead they both are same class MyClass underneath and that's why they share static variables.

Dufour answered 9/9, 2015 at 21:27 Comment(0)
M
2

They are not really shared. Because the member doesn't belong to the instance at all. A static class member belongs to the class itself. So, if you have MyClass.Number it is the same for all MyClass.Number objects because it not even depends on the object. You can even call or modify MyClass.Number without any object.

But since Foo< int > is not the same class as Foo< string > these two numbers are not shared.

An example to show this:

TestClass<string>.Number = 5;
TestClass<int>.Number = 3;

Console.WriteLine(TestClass<string>.Number);  //prints 5
Console.WriteLine(TestClass<int>.Number);     //prints 3
Moise answered 14/6, 2010 at 12:48 Comment(0)
T
-5

IMO, you need to test it, but I think that

Foo<int>.member = 1;
Foo<string>.member = 2;
Console.WriteLine (Foo<int>.member);

will output 1 because I think that, during compilation, the compilator create 1 class for every generic class you use (in you example : Foo<int> and Foo<string>).

But I'm not 100% sure =).

Remark : I think it's not a good design nor a good practice to use such kind of static attributes.

Truditrudie answered 14/6, 2010 at 12:39 Comment(1)
If not 100% sure - don't commentStercoraceous

© 2022 - 2024 — McMap. All rights reserved.