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

Это скорее документация, чем реальный вопрос. Это, кажется, еще не было решено на 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 не тот же класс, что и 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% =).

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

Другие вопросы по тегам