Почему C # не позволяет статическим методам реализовать интерфейс? - PullRequest
429 голосов
/ 03 ноября 2008

Почему 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;
    }

    ....

 }

Ответы [ 25 ]

211 голосов
/ 03 ноября 2008

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

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 и приму его в качестве указания на не будет хорошей идеей.

163 голосов
/ 03 ноября 2008

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

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

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

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

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

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

88 голосов
/ 30 октября 2011

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

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

Например:

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

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

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

  • Создать полностью отдельный фабричный класс

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

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

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

Пример: * 1 034 *

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

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

18 голосов
/ 17 ноября 2011

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

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

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

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

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

14 голосов
/ 22 августа 2012

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

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

14 голосов
/ 03 ноября 2008

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

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

14 голосов
/ 03 ноября 2008

Близорукость, наверное.

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

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

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

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

9 голосов
/ 03 ноября 2008

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

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


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;  
4 голосов
/ 13 августа 2013

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

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

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

То, что нужно 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;
    }
}

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

3 голосов
/ 26 февраля 2009

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

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

...