Почему переопределение статического метода разрешено в C # - PullRequest
7 голосов
/ 16 ноября 2010
protected static new void WhyIsThisValidCode()
{
}

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

Возьмите следующие классы.

class BaseLogger
{
    protected static string LogName { get { return null; } }

    public static void Log(string message) { Logger.Log(message, LogName); }
}

class SpecificLogger : BaseLogger
{
    protected static string LogName { get { return "Specific"; } }
}

это допустимо, и код

SpecificLogger.Log("test");

допускается также, но он не делает то, что вы думаете, глядя на код.

он вызывает Logger.Log с LogName = null.

Так почему эторазрешено?

Ответы [ 6 ]

24 голосов
/ 16 ноября 2010

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

15 голосов
/ 16 ноября 2010

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

Сокрытие полезно только в нескольких случаях. Тот, где я чаще всего сталкивался, делает тип возвращаемого значения более конкретным в производном классе. Но я никогда не сталкивался со статическими методами.

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

И это вряд ли приведет к ошибкам, так как ваш код выдает предупреждение компилятора:

Предупреждение «StaticHiding.SpecificLogger.LogName» скрывает унаследованный член «StaticHiding.BaseLogger.LogName». Используйте новое ключевое слово, если целью было скрытие.

И если вы используете new, вы должны знать, что делаете.

10 голосов
/ 16 ноября 2010

Другие отметили, что это не имеет преимущественного значения, но это все еще оставляет ваш первоначальный вопрос: почему вы можете это сделать? (Но вопрос действительно в том, «почему вы можете скрыть статические методы».)

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

Например, представьте, что компонент CompB содержит базовый класс, а другой компонент CompD содержит производный класс. В версии 1 CompB могло отсутствовать какое-либо свойство с именем LogName. Автор CompD решает добавить статическое свойство с именем LogName.

Важная вещь, которую необходимо понять на этом этапе, заключается в том, что автор v1 CompD не собирался заменять или скрывать какие-либо функции базового класса - в базовом классе не было члена с именем LogName, когда они писали этот код.

Теперь представьте, что выпущена новая версия библиотеки CompB. В этой новой версии автор добавил свойство LogName. Что должно произойти в CompD? Опции выглядят так:

  1. CompD больше не работает, потому что в LogName возникают конфликты с LogName, добавленным в CompB
  2. Каким-то образом заставить LogName CompD заменить базовое CompN LogName. (На самом деле не совсем понятно, как это могло бы работать со статиками. Хотя вы могли бы предусмотреть и нестатику.)
  3. Рассматривайте двух членов LogName как совершенно разных участников, которые имеют одинаковые имена. (На самом деле это не так - они называются BaseLogger.LogName и SpecificLogger.LogName. Но поскольку в C # нам не всегда нужно указывать имя члена класса, похоже, что это одно и то же имя. )

.NET решает сделать 3. (И он делает это как со статикой, так и с нестатикой. Если вам нужно поведение 2 - замена - на нестатику, тогда база должна быть virtual и производный класс должен пометить метод как override, чтобы было ясно, что он намеренно переопределяет метод в базовом классе. C # никогда не заставит метод производного класса заменить метод базового класса, если производный класс явно не заявил, что это то, что они хотели.) Вероятно, это будет безопасно, потому что два члена не связаны - базовое LogName даже не существовало в тот момент, когда был введен производный. И это предпочтительнее простого взлома, потому что в последней версии базового класса появился новый член.

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

Вы говорите, что поведение не то, что вы ожидаете. На самом деле это именно то, что я ожидал, и то, что вы, вероятно, хотите на практике. BaseLogger не знает, что SpecificLogger ввел свое собственное свойство LogName. (Нет механизма, с помощью которого он мог бы это сделать, потому что вы не можете переопределить статические методы.) И когда автор SpecificLogger написал это свойство LogName, помните, что они писали против v1 BaseLogger, у которого не было LogName, поэтому они не собирались что он должен заменить базовый метод либо. Поскольку ни один из классов не хочет замены, замена явно была бы неправильной вещью.

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

SpecificLogger.Log("test");

но компилятор C # видит, что SpecificLogger не предоставляет метод Log, поэтому он превращает это в:

BaseLogger.Log("test");

потому что именно там определен метод Log.

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

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

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

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

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

(я думаю, что на самом деле они должны выдавать ошибку, а не предупреждение, когда вы пропускаете новое, чтобы прояснить это.)

2 голосов
/ 16 ноября 2010

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

Это просто методы и поля в этом конкретном классе.

Может показаться, что методы и поля "унаследованы", потому что вы можете сделать SpecificLogger.Log(), но это всего лишь то, что не дает вам постоянно обращаться к базовому классу.

Статические методы и поля на самом деле являются просто глобальными методами и полями, всего лишь видом ОО.

0 голосов
/ 08 сентября 2014

, к моему удивлению, следующий код разрешен и компилируется без ошибок в .net Framework 4.5.1, VS 2013.

class A
{
    public static void Foo()
    {
    }
}

class B : A
{

}

class Program
{
    static void main(string[] args)
    {
        B.Foo();
    }
}
0 голосов
/ 16 ноября 2010

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

SpecificLogger a = new SpecificLogger();
BaseLogger b = new SpecificLogger();

Console.Write(a.Log); // Specific
Console.Write(b.Log); // null

В вашем коде метод Log фактически работает с интерфейсом BaseLogger, поскольку метод Log является частью класса BaseLogger.не переопределяйте, и когда вы хотите скрыть свойство, вы должны использовать ключевое слово new, чтобы обозначить, что вы что-то скрываете.

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