Статические члены универсального класса привязаны к конкретному экземпляру?


это скорее документация, чем реальный вопрос. Это, кажется, еще не было рассмотрено на SO (если я не пропустил его), так что здесь идет:

представьте себе универсальный класс, который содержит статические члены:

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

есть ли новый экземпляр члена для каждого конкретного класса, или есть только один экземпляр для всех классов Foo-типа?

Это можно легко проверить с помощью такого кода:

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

каков результат, и где это поведение задокументировано?

6 67

6 ответов:

A static поле является общим для всех экземпляров того же типа. Foo<int> и Foo<string> два разных типа. Это можно доказать с помощью следующей строки кода:

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

что касается того, где это задокументировано, то в разделе 1.6.5 поля спецификации языка C# (для C# 3):

статическое поле идентифицирует ровно один место хранения. Сколько бы их ни было экземпляры класса создан, есть только одна копия статическое поле.

как было сказано выше; Foo<int> и Foo<string> не являются одним и тем же классом; это два разных класса, построенных из одного и того же универсального класса. Как это происходит, описано в разделе 4.4 вышеупомянутого документа:

объявление универсального типа, само по себе, означает несвязанный универсальный тип используется в качестве "чертежа" для формирования многих различные типы, путем применения тип аргументы.

проблема здесь заключается в том, что "общие классы" вообще не являются классами.

определения общих классов - это просто шаблоны для классов, и до тех пор, пока их параметры типа не будут указаны, они являются просто куском текста (или несколькими байтами).

во время выполнения можно указать параметр типа для шаблона, тем самым оживив его и создав класс теперь полностью указанного типа. Вот почему статические свойства не являются шаблонными, и это почему вы не можете бросить между List<string> и List<int>.

это отношение как бы отражает отношение класса-объекта. Точно так же, как классы не существуют* до тех пор, пока вы не создадите экземпляр объекта из них, универсальные классы не существуют, пока вы не создадите класс на основе шаблона.

P. S. Это вполне можно объявить

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

из этого очевидно, что статические члены не могут быть разделены, так как T отличается для разных специализаций.

Они не разделяют. Не уверен, где это документально, но анализ предупреждение CA1000 (не объявляйте статические члены в универсальных типах) предупреждает только об этом из-за риска сделать код более сложным.

C# реализация дженериков более близка к C++. На обоих этих языках MyClass<Foo> и MyClass<Bar> не разделяют статические члены, но в Java они делают. В C# и C++ MyClass<Foo> внутренне создает совершенно новый тип во время компиляции, как если бы дженерики были своего рода макросами. Обычно вы можете видеть их сгенерированные имена в трассировке стека, например MyClass'1 и MyClass'2. Вот почему они не разделяют статические переменные. В Java дженерики реализуются более простым методом компилятора, генерирующим код с использованием не универсальные типы и добавление типов приведений по всему. Так что MyClass<Foo> и MyClass<Bar> Не создавайте два совершенно новых класса в Java, вместо этого они оба являются одним и тем же классом MyClass внизу и поэтому они разделяют статические переменные.

они на самом деле не разделяются. Потому что член вообще не принадлежит экземпляру. Статический член класса принадлежит самому классу. Итак, если у вас есть MyClass.Это количество является одинаковым для всех класса MyClass.Количество объектов, потому что это даже не зависит от объекта. Вы даже можете вызвать или изменить MyClass.Номер без какого-либо объекта.

но так как Foo не является тем же классом, что и Foo, эти два числа не являются общими.

пример для показа это:

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

Console.WriteLine(TestClass<string>.Number);  //prints 5
Console.WriteLine(TestClass<int>.Number);     //prints 3

ИМО, вы должны проверить его, но я думаю, что

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

выводит 1 потому что я думаю, что во время компиляции компилятор создает 1 класс для каждого универсального класса, который вы используете (в вашем примере:Foo<int> и Foo<string>).

но я не уверен на 100%=).

примечание: Я думаю, что это не хороший дизайн и не хорошая практика использовать такие статические атрибуты.