Почему я не могу наследовать статические классы? - PullRequest
206 голосов
/ 21 апреля 2009

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

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

Примерно так:

public static class Base
{
}

public static class Inherited : Base
{
}

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

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

Ответы [ 12 ]

165 голосов
/ 21 апреля 2009

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

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

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

(Мадс Торгерсен, C # Language PM)

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

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

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

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

(littleguru)

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

70 голосов
/ 21 апреля 2009

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

Итак, это:

static class Foo { }

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

.class private abstract auto ansi sealed beforefieldinit Foo
  extends [mscorlib]System.Object
 {
 }
22 голосов
/ 21 апреля 2009

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

MyStaticType.MyStaticMember();

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

MyNewType.MyStaticMember();

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

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

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

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

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

3 голосов
/ 10 марта 2011

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

3 голосов
/ 22 апреля 2009

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

2 голосов
/ 22 апреля 2009

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

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

1 голос
/ 05 июня 2017

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

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>();

К сожалению, из-за языкового ограничения "by-design" мы не можем:

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

public static class TestClass : TestClass<double>
{
}
1 голос
/ 15 мая 2016

Вы можете сделать что-то похожее на статическое наследование.

Вот трюк:

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, но статическое поле только для чтения с его безопасностью потока было достаточно хорошим.

1 голос
/ 21 апреля 2009

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

0 голосов
/ 24 мая 2019

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

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

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

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.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...