Связаны ли статические члены универсального класса с конкретным экземпляром?
Это скорее документация, чем реальный вопрос. Это, кажется, еще не было решено на SO (если я не пропустил), так что здесь идет:
Представьте себе универсальный класс, который содержит статический член:
class Foo<T> {
public static int member;
}
Существует ли новый экземпляр члена для каждого конкретного класса или только один экземпляр для всех классов Foo-типа?
Это можно легко проверить по коду:
Foo<int>.member = 1;
Foo<string>.member = 2;
Console.WriteLine (Foo<int>.member);
Каков результат и где задокументировано это поведение?
6 ответов
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>
,
Эти отношения как бы отражают отношения класса и объекта. Так же, как классы не существуют * до тех пор, пока вы не создадите из них объект, общие классы не будут существовать, пока вы не создадите класс на основе шаблона.
PS вполне можно заявить
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.Number, он одинаков для всех объектов MyClass.Number, потому что он даже не зависит от объекта. Вы даже можете вызвать или изменить MyClass.Number без каких-либо объектов.
Но поскольку 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% =).
Замечание: я думаю, что это не хороший дизайн и не хорошая практика использовать такие статические атрибуты.