Почему С# не позволяет статическим методам реализовать интерфейс?

Почему С# был разработан таким образом?

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

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

Вот пример того, что я имею в виду:

// These items will be displayed in a list on the screen.
public interface IListItem {
  string ScreenName();
  ...
}

public class Animal: IListItem {
    // All animals will be called "Animal".
    public static string ScreenName() {
        return "Animal";
    }
....
}

public class Person: IListItem {

    private string name;

    // All persons will be called by their individual names.
    public string ScreenName() {
        return name;
    }

    ....

 }

Ответ 1

Предполагая, что вы спрашиваете, почему вы не можете этого сделать:

public interface IFoo {
    void Bar();
}

public class Foo: IFoo {
    public static void Bar() {}
}

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


Чтобы реализовать ваш пример, я бы дал свойство Animal const, которое все равно позволит ему получить доступ из статического контекста и вернуть это значение в реализацию.
public class Animal: IListItem {
    /* Can be tough to come up with a different, yet meaningful name!
     * A different casing convention, like Java has, would help here.
     */
    public const string AnimalScreenName = "Animal";
    public string ScreenName(){ return AnimalScreenName; }
}

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

Ответ 2

Моя (упрощенная) техническая причина заключается в том, что статические методы не находятся в vtable, и сайт вызова выбирается во время компиляции. По той же причине вы не можете переопределить или виртуальные статические члены. Для получения более подробной информации вам понадобится кадр CS или компилятор wonk, из которых я не являюсь.

По политическим причинам я цитирует Эрика Липперта (который является компилятором, и имеет степень бакалавра математики, информатики и Прикладная математика из Университета Ватерлоо (источник: LinkedIn):

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

Обратите внимание, что Lippert оставляет место для так называемого метода типа:

То есть метод, связанный с типом (например, статическим), который не принимает аргумент "this", не содержащий NULL (в отличие от экземпляра или виртуального), но тот, где вызванный метод будет зависеть от построенного типа от T (в отличие от статики, которая должна быть определена во время компиляции).

но еще предстоит убедиться в его полезности.

Ответ 3

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

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

Например:

Repository GetRepository<T>()
{
  //need to call T.IsQueryable, but can't!!!
  //need to call T.RowCount
  //need to call T.DoSomeStaticMath(int param)
}

...
var r = GetRepository<Customer>()

К сожалению, я могу придумать только "уродливые" альтернативы:

  • Использовать отражение Уродливые и превосходят идею интерфейсов и полиморфизма.

  • Создать полностью отдельный класс factory

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

  • Произвести активацию, а затем вызвать требуемый метод интерфейса

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

Пример:

public class Customer 
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  void SomeOtherMethod() 
  { 
    //do work...
  }
}

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

public class Customer: IDoSomeStaticMath
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  //dummy instance
  public Customer() { IsDummy = true; }

  int DoSomeStaticMath(int a) { }

  void SomeOtherMethod() 
  { 
    if(!IsDummy) 
    {
      //do work...
    }
  }
}

Это явно уродливое и ненужное усложняет код для всех других методов. Очевидно, что это не элегантное решение!

Ответ 4

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

string DoSomething<T>() where T:ISomeFunction
{
  if (T.someFunction())
    ...
}

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

  • Создайте статический универсальный класс, тип параметра которого будет типом, который вы должны передать в DoSomething выше. Каждая вариация этого класса будет содержать один или несколько статических элементов, содержащих материал, относящийся к этому типу. Эта информация может быть предоставлена ​​либо путем вызова каждого класса интереса подпрограммой "информация о регистре", либо путем использования Reflection для получения информации при запуске статического конструктора изменения класса. Я считаю, что последний подход используется такими вещами, как Comparer <T> .Default().
  • Для каждого интересующего класса T определите класс или структуру, которая реализует IGetWhateverClassInfo <T> и удовлетворяет "новому" ограничению. Класс фактически не будет содержать никаких полей, но будет иметь статическое свойство, которое возвращает статическое поле с информацией о типе. Передайте тип этого класса или структуры в общую подпрограмму, о которой идет речь, которая сможет создать экземпляр и использовать его для получения информации о другом классе. Если вы используете для этой цели класс, вероятно, вы должны определить статический общий класс, как указано выше, чтобы избежать необходимости создавать новый экземпляр объекта-дескриптора каждый раз. Если вы используете структуру, стоимость создания экземпляра должна быть равна нулю, но для каждого типа структуры потребуется разное расширение процедуры DoSomething.

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

Ответ 5

Близорукость, я бы предположила.

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

IMyInterface val = GetObjectImplementingIMyInterface();
val.SomeThingDefinedinInterface();

Только с введением интерфейсов в качестве ограничений для дженериков практическое применение имело добавление статического метода к интерфейсу.

(отвечая на комментарий:) Я считаю, что теперь его изменение потребует изменения в CLR, что приведет к несовместимости с существующими сборками.

Ответ 6

Интерфейсы определяют поведение объекта.

Статические методы не определяют поведение объекта, а поведение, которое каким-то образом влияет на объект.

Ответ 7

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

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

Ответ 8

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

Как бы вы это назвали??


public interface MyInterface { void MyMethod(); }
public class MyClass: MyInterface
{
    public static void MyMethod() { //Do Something; }
}

 // inside of some other class ...  
 // How would you call the method on the interface ???
    MyClass.MyMethod();  // this calls the method normally 
                         // not through the interface...

    // This next fails you can't cast a classname to a different type... 
    // Only instances can be Cast to a different type...
    MyInterface myItf = MyClass as MyInterface;  

Ответ 9

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

T SumElements<T>(T initVal, T[] values)
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

Здесь нет полиморфизма, общий использует фактический тип объекта и вызывает оператор + =, но это не удается, поскольку он не может точно сказать, что этот оператор существует. Простое решение - указать его в ограничении; простое решение невозможно, поскольку операторы являются статическими, а статические методы не могут быть в интерфейсе и (здесь проблема) ограничения представлены как интерфейсы.

Что нужно С# - это реальный тип ограничений, все интерфейсы также будут ограничениями, но не все ограничения будут интерфейсами, тогда вы можете это сделать:

constraint CHasPlusEquals
{
    static CHasPlusEquals operator + (CHasPlusEquals a, CHasPlusEquals b);
}

T SumElements<T>(T initVal, T[] values) where T : CHasPlusEquals
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

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

Ответ 10

Поскольку интерфейсы находятся в структуре наследования, а статические методы не наследуют хорошо.

Ответ 11

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

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

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

Ответ 12

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

Ответ 13

Чтобы привести пример, где мне не хватает либо статической реализации методов интерфейса, либо того, что Марк Браккет представил как "так называемый метод типа":

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

interface IDataRow {
  string GetDataSTructre();  // How to read data from the DB
  void Read(IDBDataRow);     // How to populate this datarow from DB data
}

public class DataTable<T> : List<T> where T : IDataRow {

  public string GetDataStructure()
    // Desired: Static or Type method:
    // return (T.GetDataStructure());
    // Required: Instantiate a new class:
    return (new T().GetDataStructure());
  }

}

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

Ответ 14

Интерфейсы представляют собой абстрактные наборы определенных доступных функций.

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

Если методы были определены как статические, класс, реализующий интерфейс, не был бы таким же инкапсулированным, как это могло бы быть. Инкапсуляция - это хорошая вещь, для которой нужно стремиться в объектно-ориентированном дизайне (я не буду вдаваться в почему, вы можете прочитать это здесь: http://en.wikipedia.org/wiki/Object-oriented). По этой причине статические методы не допускаются в интерфейсах.

Ответ 15

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

Ответ 16

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

У меня была группа классов Static Business Layer, в которой реализованы методы CRUD, такие как "Create", "Read", "Update", "Delete" для каждого типа сущности типа "Пользователь", "Команда" и т.д. Затем я создал базовый элемент управления, который имел абстрактное свойство для класса Business Layer, которое реализовало методы CRUD. Это позволило мне автоматизировать операции "Создать", "Читать", "Обновить", "Удалить" из базового класса. Мне пришлось использовать Singleton из-за ограничения Static.

Ответ 17

Большинство людей, похоже, забывают, что в классах ООП тоже есть объекты, поэтому у них есть сообщения, которые по какой-то причине С# называет "статический метод". Тот факт, что различия существуют между объектами экземпляра и объектами класса, показывает только недостатки или недостатки в языке. Оптимист о С#, хотя...

Ответ 18

OK вот пример необходимости использования метода типа. Я создаю один из набора классов, основанных на некотором исходном XML. Поэтому у меня есть

  static public bool IsHandled(XElement xml)

которая в свою очередь вызывается для каждого класса.

Функция должна быть статической, так как иначе мы теряем время, создавая несоответствующие объекты. Как указывает @Ian Boyde, это может быть сделано в классе factory, но это просто добавляет сложности.

Было бы неплохо добавить его в интерфейс, чтобы заставить разработчиков классов реализовать его. Это не вызовет значительных накладных расходов - это только проверка времени компиляции/ссылки и не влияет на таблицу vtable.

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

Таким образом, это небольшая потенциальная функция, которая в балансе, вероятно, лучше всего оставить.

Ответ 19

Тот факт, что статический класс реализован на С# Microsoft, создавая специальный экземпляр класса со статическими элементами, - это просто странность того, как достигается статическая функциональность. Это не теоретическая точка.

Интерфейс ДОЛЖЕН быть дескриптором интерфейса класса - или как он взаимодействует, и должен включать в себя взаимодействия, которые являются статическими. Общее определение интерфейса (от Meriam-Webster): место или область, в которой разные вещи встречаются и общаются друг с другом или взаимодействуют друг с другом. Когда вы полностью опускаете статические компоненты класса или статические классы, мы игнорируем большие разделы того, как взаимодействуют эти плохие мальчики.

Вот очень ясный пример того, где можно использовать интерфейсы со статическими классами:

public interface ICrudModel<T, Tk>
{
    Boolean Create(T obj);
    T Retrieve(Tk key);
    Boolean Update(T obj);
    Boolean Delete(T obj);
}

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

Ответ 20

С# и CLR должны поддерживать статические методы в интерфейсах, как это делает Java. Статический модификатор является частью определения контракта и имеет смысл, в частности, что поведение и возвращаемое значение не меняются в зависимости от конкретного экземпляра, хотя он может по-прежнему отличаться от вызова к вызову.

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

Ответ 21

Я думаю, что короткий ответ - "потому что он имеет нулевую полезность". Чтобы вызвать метод интерфейса, вам нужен экземпляр типа. Из методов экземпляров вы можете вызвать любые статические методы, которые вы хотите.

Ответ 22

Я думаю, что вопрос заключается в том, что для С# требуется другое ключевое слово для именно такой ситуации. Вам нужен метод, возвращающее значение которого зависит только от типа, на который он вызывается. Вы не можете называть его "статическим", если указанный тип неизвестен. Но как только тип станет известен, он станет статичным. "Неопределенная статика" - это идея - она ​​еще не статична, но как только мы узнаем тип приема, это будет. Это совершенно хорошая концепция, поэтому программисты продолжают ее просить. Но это не совсем вписывалось в то, как дизайнеры думали о языке.

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

public interface IZeroWrapper<TNumber> {
  TNumber Zero {get;}
}

public class DoubleWrapper: IZeroWrapper<double> {
  public double Zero { get { return 0; } }
}

Ответ 23

В соответствии с объектно-ориентированной концепцией Интерфейс, реализуемый классами и иметь контракт на доступ к этим реализованным функциям (или методам), используя объект.

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

Ответ 24

Концептуально нет причины, по которой интерфейс не мог бы определить контракт, включающий статические методы.

Для текущей реализации языка С# ограничение связано с разрешением наследования базового класса и интерфейсов. Если "класс SomeBaseClass" реализует "интерфейс ISomeInterface" и "класс SomeDerivedClass: SomeBaseClass, ISomeInterface" также реализует интерфейс, статический метод для реализации метода интерфейса может не скомпилироваться, поскольку статический метод не может иметь такую же сигнатуру, как метод экземпляра (что присутствовать в базовом классе для реализации интерфейса).

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

Таким образом, это просто сводится к ограничению конфликта имен С#, например, и статических методов с одним и тем же именем в наследовании. Нет причин, по которым С# не может быть "обновлен" для поддержки контрактов статических методов (интерфейсов).

Ответ 25

не делайте этого, попробуйте вместо этого абстрактные классы

public abstract class ExampleBase
{
    /// <summary>
    /// Do it
    /// </summary>
    public virtual abstract static void DoIt();
}

Ответ 26

Когда класс реализует интерфейс, он создает экземпляр для членов интерфейса. Хотя статический тип не имеет экземпляра, нет смысла иметь статические подписи в интерфейсе.