Почему я не могу наследовать статические классы?

У меня есть несколько классов, которые действительно не нуждаются в каком-либо состоянии. С организационной точки зрения я хотел бы поместить их в иерархию.

Но, похоже, я не могу объявить наследование для статических классов.

Что-то вроде этого:

public static class Base
{
}

public static class Inherited : Base
{
}

не будет работать.

Почему дизайнеры языка закрыли эту возможность?

Ответ 1

Цитата из здесь:

Это на самом деле по дизайну. Кажется, нет никакой веской причины наследовать статический класс. Он имеет общедоступные статические члены, к которым вы всегда можете получить доступ через само название класса. Единственные причины, которые я видел для наследования статического материала, были плохими, например, сохранение нескольких символов ввода.

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

(Mads Torgersen, С# Language PM)

Другие мнения channel9

Наследование в .NET работает только на базе экземпляра. Статические методы определяются на уровне типа не на уровне экземпляра. Вот почему переопределение не работает со статическими методами/свойствами/событиями...

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

Если вы вызываете метод экземпляра в .NET, вы всегда даете ему текущий экземпляр. Это скрыто средой выполнения .NET, но это происходит. Каждый метод экземпляра имеет в качестве первого аргумента указатель (ссылку) на объект, над которым запущен метод. Это не происходит со статическими методами (поскольку они определены на уровне типа). Как компилятор должен выбрать способ вызова?

(littleguru)

И как ценная идея, у littleguru есть частичный "обходной путь" для этой проблемы: шаблон Singleton.

Ответ 2

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

Итак, это:

static class Foo { }

компилируется в этот IL:

.class private abstract auto ansi sealed beforefieldinit Foo
  extends [mscorlib]System.Object
 {
 }

Ответ 3

Подумайте об этом так: вы получаете доступ к статическим членам через имя типа, например:

MyStaticType.MyStaticMember();

Если бы вы наследовали этот класс, вам нужно было бы получить к нему доступ через новое имя типа:

MyNewType.MyStaticMember();

Таким образом, новый элемент не имеет отношения к оригиналу при использовании в коде. Невозможно было бы использовать любые отношения наследования для таких вещей, как полиморфизм.

Возможно, вы думаете, что хотите просто расширить некоторые элементы исходного класса. В этом случае ничего не мешает вам просто использовать член оригинала в совершенно новом типе.

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

Возможно, вы захотите передать статическую Type функции во время выполнения и вызвать метод для этого типа, не зная точно, что делает этот метод. В этом случае вы можете использовать интерфейс.

Итак, в конце концов вы ничего не получаете от наследования статических классов.

Ответ 4

То, чего вы хотите достичь, используя иерархию классов, может быть достигнуто только через пространство имен. Поэтому языки, поддерживающие namespapces (например, С#), не смогут использовать иерархию классов статических классов. Поскольку вы не можете создать экземпляр какого-либо из классов, все, что вам нужно, это иерархическая организация определений классов, которую вы можете получить с помощью пространств имен

Ответ 5

Вместо этого вы можете использовать композицию... это позволит вам получить доступ к объектам класса из статического типа. Но все же can not реализует интерфейсы или абстрактные классы

Ответ 6

Хотя вы можете получить доступ к "унаследованным" статическим членам через имя унаследованных классов, статические члены на самом деле не наследуются. Это отчасти потому, что они не могут быть виртуальными или абстрактными и не могут быть отменены. В вашем примере, если вы объявили Base.Method(), компилятор будет переназначать вызов Inherited.Method() обратно в Base.Method() в любом случае. Вы также можете вызвать Base.Method() явно. Вы можете написать небольшой тест и увидеть результат с помощью Reflector.

Итак... если вы не можете наследовать статические члены, и если статические классы могут содержать только статические члены, то что хорошего наследует статический класс?

Ответ 7

Хммм... было бы совсем иначе, если бы у вас были нестатические классы, заполненные статическими методами.?

Ответ 8

Вы можете сделать что-то, что будет выглядеть как статическое наследование.

Вот трюк:

public abstract class StaticBase<TSuccessor>
    where TSuccessor : StaticBase<TSuccessor>, new()
{
    protected static readonly TSuccessor Instance = new TSuccessor();
}

Затем вы можете сделать это:

public class Base : StaticBase<Base>
{
    public Base()
    {
    }

    public void MethodA()
    {
    }
}

public class Inherited : Base
{
    private Inherited()
    {
    }

    public new static void MethodA()
    {
        Instance.MethodA();
    }
}

Класс Inherited не является статическим, но мы не можем его создавать. Он фактически унаследовал статический конструктор, который строит Base, а все свойства и методы Base доступны как статические. Теперь остается только сделать статические обертки для каждого метода и свойства, которые вам нужно подвергнуть статическому контексту.

Есть недостатки, такие как необходимость ручного создания статических методов-оберток и ключевое слово new. Но этот подход помогает поддерживать то, что действительно похоже на статическое наследование.

P.S. Мы использовали это для создания скомпилированных запросов, и это фактически может быть заменено ConcurrentDictionary, но статическое поле только для чтения с безопасностью потока было достаточно хорошим.

Ответ 9

Обходной путь, который вы можете сделать, - это не использовать статические классы, но скрыть конструктор, чтобы классы-статические члены были единственными, доступными вне класса. Результат - наследуемый "статический" класс по существу:

public class TestClass<T>
{
    protected TestClass()
    { }

    public static T Add(T x, T y)
    {
        return (dynamic)x + (dynamic)y;
    }
}

public class TestClass : TestClass<double>
{
    // Inherited classes will also need to have protected constructors to prevent people from creating instances of them.
    protected TestClass()
    { }
}

TestClass.Add(3.0, 4.0)
TestClass<int>.Add(3, 4)

// Creating a class instance is not allowed because the constructors are inaccessible.
// new TestClass();
// new TestClass<int>();

К сожалению, из-за ограниченного языкового ограничения "по дизайну" мы не можем:

public static class TestClass<T>
{
    public static T Add(T x, T y)
    {
        return (dynamic)x + (dynamic)y;
    }
}

public static class TestClass : TestClass<double>
{
}

Ответ 10

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

Класс может быть объявлен static, что указывает на то, что он содержит только статические члены. Невозможно использовать новое ключевое слово для создания экземпляров статического класса. Статические классы загружаются автоматически с помощью среды CLR, основанной на стандарте .NET Framework, когда загружается программа или пространство имен, содержащее класс.

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

Ниже перечислены основные функции статического класса:

  • Они содержат только статические элементы.

  • Они не могут быть созданы.

  • Они запечатаны.

  • Они не могут содержать конструкторы экземпляров (руководство по программированию на С#).

Создание статического класса, таким образом, в основном такое же, как создание класса, содержащего только статические элементы и частный конструктор. Частный конструктор предотвращает создание экземпляра класса.

Преимущество использования статического класса заключается в том, что компилятор может проверить, чтобы случайно не добавлялись члены экземпляра. Компилятор гарантирует, что экземпляры этого класса не могут быть созданы.

Статические классы запечатаны и поэтому не могут быть унаследованы. Они не могут наследовать ни один класс, кроме Object. Статические классы не могут содержать конструктор экземпляра; однако они могут иметь статический конструктор. Для получения дополнительной информации см. "Статические конструкторы" (Руководство по программированию на С#).

Ответ 11

Мой ответ: неудачный выбор дизайна. ;-)

Это интересная дискуссия, посвященная влиянию синтаксиса. Суть аргумента, на мой взгляд, заключается в том, что проектное решение привело к запечатанным статическим классам. Акцент на прозрачности имен статических классов, появляющихся на верхнем уровне, вместо того, чтобы прятаться ("сбивать с толку") за дочерними именами? Можно запечатлеть языковую реализацию, которая может напрямую обращаться к базе или к ребенку, что сбивает с толку.

Псевдо-пример, предполагающий статическое наследование, был определен некоторым образом.

public static class MyStaticBase
{
    SomeType AttributeBase;
}

public static class MyStaticChild : MyStaticBase
{
    SomeType AttributeChild;
}

приведет к:

 // ...
 DoSomethingTo(MyStaticBase.AttributeBase);
// ...

который может (будет?) влиять на то же хранилище, что и

// ...
DoSomethingTo(MyStaticChild.AttributeBase);
// ...

Очень запутанно!

Но ждать! Как компилятор будет иметь дело с MyStaticBase и MyStaticChild, имеющими одинаковую сигнатуру, определенную в обоих? Если ребенок переопределяет, чем мой приведенный выше пример, НЕ изменится то же хранилище? Это приводит к еще большему замешательству.

Я считаю, что существует сильное информационное пространство для ограниченного статического наследования. Подробнее о пределах в ближайшее время. Этот псевдокод показывает значение:

public static class MyStaticBase<T>
{
   public static T Payload;
   public static void Load(StorageSpecs);
   public static void Save(StorageSpecs);
   public static SomeType AttributeBase
   public static SomeType MethodBase(){/*...*/};
}

Тогда вы получите:

public static class MyStaticChild : MyStaticBase<MyChildPlayloadType>
{
   public static SomeType AttributeChild;
   public static SomeType SomeChildMethod(){/*...*/};
   // No need to create the PlayLoad, Load(), and Save().
   // You, 'should' be prevented from creating them, more on this in a sec...
} 

Использование выглядит так:

// ...
MyStaticChild.Load(FileNamePath);
MyStaticChild.Save(FileNamePath);
doSomeThing(MyStaticChild.Payload.Attribute);
doSomething(MyStaticChild.AttributeBase);
doSomeThing(MyStaticChild.AttributeChild);
// ...

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

Статика (синглтоны и другие формы глобалов) часто возникает вокруг хранилища конфигурации. Статическое наследование позволило бы четко представить такое распределение ответственности в синтаксисе, чтобы оно соответствовало иерархии конфигураций. Хотя, как я показал, существует большой потенциал для большой двусмысленности, если будут реализованы базовые концепции статического наследования.

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

  1. Нет переопределения ничего. Дочерний объект не может заменить базовые атрибуты, поля или методы... Перегрузка должна быть в порядке, если есть разница в сигнатуре, позволяющая компилятору разобраться с дочерним и базовым.
  2. Разрешить только общие статические базы, вы не можете наследовать от неуниверсальных статических баз.

Вы все еще можете изменить это же хранилище через общую ссылку MyStaticBase<ChildPayload>.SomeBaseField. Но вы были бы обескуражены, так как общий тип должен быть указан. В то время как MyStaticChild.SomeBaseField ссылка будет чище: MyStaticChild.SomeBaseField.

Я не пишу компилятор, поэтому я не уверен, что что-то упускаю из-за трудностей реализации этих ограничений в компиляторе. Тем не менее, я твердо убежден в том, что информационное пространство необходимо для ограниченного статического наследования, и основной ответ заключается в том, что вы не можете этого сделать из-за плохого (или слишком простого) выбора дизайна.

Ответ 12

Когда мы создаем статический класс, содержащий только статические члены и частный конструктор. Единственная причина в том, что статический конструктор не позволяет экземпляру создавать экземпляр, поскольку мы не можем наследовать статический класс. Единственный способ получить доступ к член статического класса с использованием самого имени класса. Попробуйте наследовать статический класс, это не очень хорошая идея.