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

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

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

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

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

// 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;
    }

    ....

 }

24 ответов


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

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 blob и возьму его как указание на то, что он может не очень хорошая идея.


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

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

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

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

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

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


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

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

например:

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

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

  • использовать отражение Некрасиво и бьет идею интерфейсов и полиморфизма.

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

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

  • создать экземпляр, а затем вызвать нужный метод интерфейса

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

пример:

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

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

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

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

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...
    }
  }
}

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


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

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

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

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

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


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

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


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

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


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

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


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;  

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

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

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

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

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;
    }
}

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


потому что интерфейсы в структуре наследование и статические методы не наследуются.


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

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

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


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


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

при чтении из хранилища базы данных у нас есть общий класс 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 требуется только один раз для каждой таблицы для чтения, накладные расходы для создания еще одного экземпляра минимальны. Впрочем, в данном случае было бы неплохо.


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


интерфейсы-это абстрактные наборы определенных доступных функций.

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

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


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

У меня была куча статических классов бизнес-уровня, которые реализовали методы CRUD, такие как" Create"," Read"," Update"," Delete "для каждого типа сущности, таких как" User"," Team", ect.. Затем я создал базовый элемент управления, который имел абстрактное свойство для класса бизнес-слоя, реализовавшего методы CRUD. Это позволило мне автоматизировать Операции" создать"," прочитать"," обновить"," удалить " из базового класса. Мне пришлось использовать Синглтон из-за статического ограничения.


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


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

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

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

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

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


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

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


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


OK вот пример необходимости "метода типа". Я создаю один из набора классов на основе некоторого исходного XML. Так что у меня есть

  static public bool IsHandled(XElement xml)

функция, которая вызывается по очереди для каждого класса.

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

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

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

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


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

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

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

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

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

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


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