Чому C # не дозволяє статичним методам реалізувати інтерфейс?


447

Чому 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;
    }

    ....

 }


1
Дивіться, як ви можете поєднати статичну поведінку з успадкуванням або реалізацією інтерфейсу: stackoverflow.com/a/13567309/880990
Олів'є Якот-Дескомбс

1
IListItem.ScreenName() => ScreenName()(використовуючи синтаксис C # 7) реалізує метод інтерфейсу явно, викликаючи статичний метод. Речі стають некрасивими, коли ви додасте до цього спадщину (вам доведеться знову доповнити інтерфейс)
Jeroen Mostert

2
Просто дайте всім знати, що чекання закінчилося! C # 8.0 має методи статичного інтерфейсу: dotnetfiddle.net/Lrzy6y (хоча вони працюють трохи інакше, як ОП хотів, щоб вони працювали - вам не доведеться їх реалізовувати)
Джеймі Твеллс

Відповіді:


221

Припускаючи, що ви запитуєте, чому ви не можете цього зробити:

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, і вважаю це свідченням того, що це може бути не бути гарною ідеєю.


6
Ідеальний приклад! Але я не впевнений, що я розумію ваші міркування. Напевно, компілятор міг бути розроблений і для того, щоб розглянути членів статуту? Хіба в примірниках немає таблиці адрес для реалізації цих методів? Не могли стати статичні методи включені до цієї таблиці?
Крамій

12
Є випадок, коли це може бути корисним. Наприклад, я хочу, щоб усі виконавці реалізували метод GetInstance, який приймає аргумент XElement. Я не можу визначити це як статичний метод в інтерфейсі, ні вимагати від інтерфейсу підпису конструктора.
олекс

25
Дуже багато людей зважили на обидві сторони: від "Це не має сенсу" до "Це помилка, я хочу, щоб ти міг". (Я думаю, що для цього є дійсні випадки використання. Ось так я і опинився тут.) Як вирішення, метод екземпляра може просто делегувати статичний метод.
harpo

5
Ви також можете просто реалізувати фрагмент як метод розширення на базовому інтерфейсі, як у: public static object MethodName(this IBaseClass base)у статичному класі. Мінусом, однак, є те, що на відміну від успадкування інтерфейсу - це не змушує / не дозволяє окремим спадкоємцям добре переосмислити методологію.
Трой Алфорд

8
Було б багато сенсу з дженериками. Наприклад, анулює щось <T> (), де T: ISomeInterface {new T (). DoSomething1 (); T.DoSomething2 (); }
Франсіско Райан Толмаскі I

173

Моя (спрощена) технічна причина полягає в тому, що статичні методи відсутні у vtable , а сайт виклику вибирається під час компіляції. Це та сама причина, що у вас не може бути перекритих або віртуальних статичних членів. Для отримання більш детальної інформації вам знадобиться CS градус або компілятор wink - з яких я ні один.

З політичної причини я наводжу Еріка Ліпперта (який є компілятором, який мав бакалавр математики, інформатики та прикладної математики з Університету Ватерлоо (джерело: LinkedIn ):

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

Зауважте, що Ліпперт залишає місце для так званого методу типу:

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

але поки що не треба переконатись у його корисності.


5
Чудово, це відповідь, яку я хотів написати - я просто не знав деталей реалізації.
Кріс Марасті-Георг

5
Чудова відповідь. І я хочу цей "тип методу"! Буде корисно стільки разів (подумайте про метадані для типу / класу).
Філіп Добмайєр

23
Це правильна відповідь. Ви передаєте кому-небудь інтерфейс, він повинен знати, як викликати метод. Інтерфейс - просто таблиця віртуальних методів . У вашого статичного класу цього немає. Абонент не знає, як викликати метод. (Перед тим, як я прочитав цю відповідь я думав , C # просто був педантичним. Тепер я розумію , що це технічне обмеження, що накладається , що інтерфейс є ). Інші люди поговорять з вами про те, як це поганий дизайн. Це не поганий дизайн - це технічне обмеження.
Ян Бойд

2
+1 за те, що насправді відповідав на питання і не був педантичним і соплі. Як сказав Ієн Бойд: "Це не поганий дизайн - це технічне обмеження".
Джошуа Печ

3
Цілком можливо генерувати об’єкт для статичного класу з пов'язаним vtable. Подивіться, як Scala обробляє objects та як їм дозволено реалізовувати інтерфейси.
Себастьян Граф

97

Більшість відповідей тут, здається, пропускають цілий пункт. Поліморфізм можна використовувати не тільки між екземплярами, але і між типами. Це часто потрібно, коли ми використовуємо дженерики.

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

Наприклад:

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

Для того, щоб використати миттєві рішення для вирішення проблеми статичного інтерфейсу, нам потрібно зробити наступне:

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

Це очевидно негарно, а також непотрібно ускладнює код для всіх інших методів. Очевидно, і не елегантне рішення!


35
+1 за "Більшість відповідей тут, здається, не вистачає всієї точки."; неймовірно, як здається, що майже всі відповіді ухиляються від основної частини питання, щоб заглибитися в переважно невикористану багатослівність ...

5
@Chris Ось конкретний приклад, який змусив мене знову натиснути це обмеження. Я хочу додати інтерфейс IResettable до класів, щоб вказати, що вони кешують певні дані в статичних змінних, які можуть бути скинуті адміністратором сайту (наприклад, список категорій замовлення, набір ставок vat, список категорій, отриманих із зовнішнього API) щоб зменшити звернення до БД та зовнішніх API, це, очевидно, використовує статичний метод скидання. Це дозволяє мені просто автоматизувати виявлення, які класи можна скинути. Я все ще можу це зробити, але метод не застосовується або автоматично додається в IDE і покладається на надію.
mattmanser

6
@Chris Я не погоджуюся, масовий перебір. Це завжди ознака мовної вади, коли більше архітектури є «найкращим» рішенням. Пам’ятаєте всі шаблони, про які вже ніхто не говорить, оскільки C # отримав загальну інформацію та анонімні методи?
маттмансер

3
Ви не можете використовувати "де T: IQueryable, T: IDoSomeStaticMath" або подібне?
Роджер Уіллокс

2
@ ChrisMarasti-Georg: Дещо брудним, але цікавим способом навколо цього є ця маленька конструкція:, public abstract class DBObject<T> where T : DBObject<T>, new()а потім зробити всі класи БД успадкованими як DBObject<T>. Тоді ви можете зробити статичне функцію повернення за ключем із поверненням типу T в абстрактному надкласі, змусити цю функцію створити новий об'єкт T, а потім викликати захищений String GetRetrieveByKeyQuery()(визначений як абстрактний надклас) на цьому об'єкті, щоб отримати власне запит для виконання. Хоча це може стати
непритомною

20

Я знаю, що це старе питання, але цікаво. Приклад - не найкращий. Я думаю, що було б набагато зрозуміліше, якби ви показали випадок використання:

рядок DoSomething <T> (), де T: ISomeFunction
{
  якщо (T.someFunction ())
    ...
}

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

  1. Створіть статичний загальний клас, параметр типу якого буде типом, який ви переходите до DoSomething вище. Кожна зміна цього класу матиме один або більше статичних членів, що містять речі, пов'язані з цим типом. Ця інформація може бути надана або за допомогою кожного класу, що цікавить, викликати процедуру "реєстрації інформації", або за допомогою Reflection, щоб отримати інформацію, коли запускається статичний конструктор зміни класу. Я вважаю, що останній підхід використовується такими речами, як Порівняння <T> .Default ().
  2. Для кожного цікавого класу T визначте клас або структуру, яка реалізує IGetWeverClassInfo <T> і задовольняє "нове" обмеження. Клас насправді не містить жодних полів, але матиме статичну властивість, яка повертає статичне поле з інформацією про тип. Передайте тип цього класу або структури відповідній загальній процедурі, яка зможе створити екземпляр і використовувати його для отримання інформації про інший клас. Якщо ви використовуєте клас для цієї мети, вам, мабуть, слід визначити статичний загальний клас, як зазначено вище, щоб уникнути необхідності кожного разу створювати новий екземпляр-дескриптор-об'єкт. Якщо ви використовуєте структуру, вартість інстанції повинна бути нульовою, але кожен різний тип структури вимагає різного розширення програми DoSomething.

Жоден із цих підходів не є дійсно привабливим. З іншого боку, я б очікував, що якби існували механізми CLR для чистого забезпечення такого функціоналу, .net дозволив би вказати параметризовані "нові" обмеження (оскільки, мабуть, знає, чи має клас конструктор з певним підписом, здавалося б бути порівнянним за труднощами з тим, щоб знати, чи є у нього статичний метод з певним підписом).


16

Я б здогадувався короткозорістю.

Коли спочатку розроблялися, інтерфейси призначалися лише для використання з екземплярами класу

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

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

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


Саме в контексті генерики я вперше зіткнувся з проблемою, але мені цікаво, чи включення статичних методів в інтерфейси може бути корисним і в інших контекстах? Чи є причина, чому речі неможливо змінити?
Крамій

я теж стикаюся з цим при реалізації загального класу, який вимагає, щоб тип параметра створював себе за допомогою деяких параметрів. Оскільки новий () не може прийняти жодного. Ви придумали, як це зробити, Крамі?
Том

1
@Kramii: Контракти для статичних API. Я не хочу, щоб об'єкт об'єкта, був лише гарантією певного підпису, наприклад. IMatrixMultiplier або ICustomSerializer. Функції / Дії / Делегати як члени класу роблять трюк, але IMO це іноді здається завищеним, і може бути заплутаним для недосвідчених, які намагаються розширити API.
Девід Кукча

14

Інтерфейси задають поведінку об'єкта.

Статичні методи визначають не поведінку об'єкта, а поведінку, яка певним чином впливає на об'єкт.


71
Вибачте .. Я не впевнений, що це правильно! Інтерфейс не визначає поведінку. Інтерфейс визначає набір іменованих операцій. Два класи могли реалізувати метод інтерфейсу, щоб вести себе абсолютно по-різному. Отже, інтерфейс взагалі не визначає поведінку. Клас, який реалізує це робить.
Скотт Ленгем

1
Сподіваюся, ви не думаєте, що я вибагливий .. але я думаю, що це важлива відмінність для того, щоб хтось навчався ОО розуміти.
Скотт Ленгем

4
Інтерфейс повинен визначати договір, що включає поведінку та презентацію. Ось чому зміна поведінки інтерфейсного виклику - це ні-ні, оскільки обидва повинні бути виправлені. Якщо у вас є інтерфейс, де виклик діє по-іншому (тобто IList.Add зробив видалення), це було б неправильно.
Джефф Йейтс

14
Що ж, так, вам би в голову викрити, щоб визначити спосіб поводити себе недобросовісно до свого імені. Якби у вас був IAlertService.GetAssistance (), його поведінка може полягати в тому, щоб спалахнути світло, запустити сигнал тривоги або ткнути в очі палицею.
Скотт Ленгем

1
Реалізація також зможе записати у файл журналу. Така поведінка не вказана в інтерфейсі. Але, можливо, ти маєш рацію. Дійсно слід поважати поведінку щодо "отримання допомоги".
Скотт Ленгем

14

Наскільки інтерфейси являють собою "контракти", статичним класам здається тихою для реалізації інтерфейсів.

Наведені вище аргументи, схоже, не вистачають цього пункту щодо контрактів.


2
Я повністю згоден з цією простою, але ефективною відповіддю. Що було б цікаво в "статичному інтерфейсі", це те, що він би представляв контракт. Можливо, це не слід називати "статичним інтерфейсом", але ми все одно пропускаємо конструкцію. Наприклад, перевірте офіційний документ .NET про інтерфейс ICustomMarshaler. Він вимагає, щоб клас, який реалізує його, "додати статичний метод, званий GetInstance, який приймає String як параметр і має тип повернення ICustomMarshaler". Це серйозно виглядає як визначення "статичного інтерфейсу" на звичайній англійській
мові,

@SimonMourier Ця документація могла бути написана чіткіше, але ви її неправильно трактуєте. Не інтерфейс ICustomMarshaler вимагає статичного методу GetInstance. Цього вимагає атрибут коду [MarshalAs]. Вони використовують заводський зразок, щоб дозволити атрибуту отримати екземпляр доданого маршалера. На жаль, вони повністю забули включити документування вимоги GetInstance на сторінку документації MarshalAs (вона показує лише приклади з використанням вбудованих реалізацій маршалінгу).
Скотт Гартнер

@ScottGartner - Не розумійте своєї точки зору. msdn.microsoft.com/en-us/library/… чітко зазначає: "Окрім реалізації інтерфейсу ICustomMarshaler, користувацькі маршрутизатори повинні реалізувати статичний метод під назвою GetInstance, який приймає String як параметр і має тип повернення ICustomMarshaler. Це статичний метод викликається загальним мовним інтерфейсом рівня COM для виконання, щоб створити екземпляр спеціального маршалера. " Це, безумовно, статичне визначення контракту.
Саймон Мур’є

9

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

Як би ти це назвав ??


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;  

1
Інші мови (наприклад, Java) дозволяють викликати статичні методи з екземплярів об'єкта, хоча ви отримаєте попередження про те, що вам слід викликати їх із статичного контексту.
Кріс Марасті-Георг

Дозволити виклик статичного методу з екземпляра дозволено також у .Net. Це різна річ. Якщо статичний метод реалізував інтерфейс, ви можете викликати його БЕЗ екземпляра. ТО, що не має сенсу. Пам'ятайте, що ви не можете розмістити реалізацію в інтерфейсі, вона повинна бути в класі. Отже, якби для реалізації цього інтерфейсу було визначено п’ять різних класів, і кожен з них відрізнявся реалізацією цього статичного методу, який використовував би компілятор?
Чарльз Бретана

1
@CharlesBretana у випадку generics, той для типу, який передали. Я бачу велику корисність у наявності інтерфейсів для статичних методів (або, якщо ви хочете, назвемо їх "статичними інтерфейсами" і дозволити класу визначати обидва статичні інтерфейси. і інтерфейси примірника). Тож якщо я маю, Whatever<T>() where T:IMyStaticInterfaceя можу зателефонувати Whatever<MyClass>()і мати T.MyStaticMethod()всередині Whatever<T>()реалізації, не потребуючи примірника. Метод виклику визначався б під час виконання. Це можна зробити за допомогою роздумів, але "контракту" не можна примушувати.
Jcl

4

Щодо статичних методів, що використовуються в негенеральних контекстах, я погоджуюся, що не має особливого сенсу допускати їх до інтерфейсів, оскільки ви б не змогли їх зателефонувати, якби у вас все-таки було посилання на інтерфейс. Однак в мовному дизайні є основна дірка, створена за допомогою інтерфейсів НЕ в поліморфному контексті, а в загальному. У цьому випадку інтерфейс - це зовсім не інтерфейс, а скоріше обмеження. Оскільки 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;
    }
}

Вже було багато розмов про те, щоб зробити ІАрифметику для всіх числових типів для реалізації, але існує стурбованість ефективністю, оскільки обмеження не є поліморфною конструкцією, внаслідок чого обмеження CArithmetic вирішило б цю проблему.


Оператори в C # є статичними, тому це все ще не має сенсу.
Джон Сондерс

У цьому суть, обмеження підтверджує, що TYPE (не екземпляр) має оператора + (і шляхом розширення оператора + =) і дозволяє генерувати шаблон, тоді, коли відбувається фактична заміна шаблону, гарантується, що використовується типу, у якого є цей оператор (або інший статичний метод.) Ви можете сказати: SumElements <int> (0, 5); що могло б це встановити: SumElements (int initVal, int [] значення) {foreach (var v у значеннях) {initVal + = v; }}, що, звичайно, має ідеальний сенс
Джеремі Соренсен

1
Пам'ятайте, що це не шаблон. Це дженерики, що не те саме, що шаблони C ++.
Джон Сондерс

@JohnSaunders: Generics - це не шаблони, і я не знаю, як їх можна розумно змусити працювати зі статично пов'язаними операторами, але в загальних контекстах є багато випадків, коли може бути корисним вказати, що у a Tповинен бути статичний член (наприклад, фабрика, яка виробляє Tекземпляри). Навіть без змін часу виконання, я думаю, можна було б визначити конвенції та допоміжні методи, які дозволяли б мовам ефективно реалізувати таке поняття, як синтаксичний цукор, ефективно та сумісно. Якщо для кожного інтерфейсу з віртуальними статичними методами існував клас помічників ...
supercat

1
@JohnSaunders: Проблема полягає не в тому, що метод реалізує інтерфейс, а в тому, що компілятор не може вибрати віртуальний метод для виклику, не маючи примірника об'єкта, на якому базуватиметься вибір. Рішення полягає в тому, щоб відправити виклик "статичного інтерфейсу" не за допомогою віртуальної диспетчеризації (яка не працюватиме), а замість цього виклик до загального статичного класу. Родова відправка на основі типу не вимагає наявності примірника, а лише мати тип.
supercat

3

Оскільки інтерфейси є в структурі успадкування, а статичні методи не успадковують добре.


3

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

Були б нескінченні дебати про те, чи має це значення, що є найкращою практикою та чи є проблеми з ефективністю, що роблять це так чи інакше. Просто не підтримуючи його, C # рятує нас від цього турбуватися.

Також ймовірно, що компілятор, який відповідав цьому бажанням, втратить деякі оптимізації, які можуть спричинити більш суворий поділ між екземпляром та статичними методами.


Цікаво, що статичним методом цілком можливо викликати обидва типи рекламних екземплярів у VB.Net (навіть якщо IDE дає попередження в останньому випадку). Це, здається, не є проблемою. Ви можете мати рацію щодо оптимізації.
Крамій

3

Ви можете вважати статичні методи та нестатичні методи класу як різні інтерфейси. При виклику статичні методи вирішаються до однотонного об’єкта статичного класу, а нестатичні - до екземпляра класу, з яким ви маєте справу. Отже, якщо ви використовуєте статичні та нестатичні методи в інтерфейсі, ви ефективно оголосите два інтерфейси, коли ми дійсно хочемо, щоб інтерфейси використовувались для доступу до однієї згуртованої речі.


Це цікава POV, і, мабуть, та, про яку мали на увазі дизайнери C #. Я буду думати про статичних членів по-іншому відтепер.
Крамій

3

Наведіть приклад, коли мені не вистачає статичної реалізації методів інтерфейсу, або що ввів Марк Брокетт як "так званий тип методу":

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


1

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


1

Інтерфейси - це абстрактні набори визначених доступних функціональних можливостей.

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

Якби методи були визначені як статичні, клас, що реалізує інтерфейс, був би не таким інкапсульованим, як міг би бути. Інкапсуляція - це хороша річ, на яку слід прагнути в об'єктно-орієнтованому дизайні (я не буду вникати в це, чому ви можете прочитати це тут: http://en.wikipedia.org/wiki/Object-oriented ). З цієї причини статичні методи не дозволені в інтерфейсах.


1
Так, статика в оголошенні інтерфейсу була б нерозумною. Клас не повинен змушувати певний спосіб реалізовувати інтерфейс. Однак чи обмежує C # клас реалізацією інтерфейсу за допомогою нестатичних методів. Це має сенс? Чому?
Крамій

Ви не можете використовувати ключове слово "статичний". Немає жодних обмежень, тому що вам не потрібно статичне ключове слово, щоб написати метод, який веде себе статично. Просто напишіть метод, який не має доступу до жодного з членів його об'єкта, тоді він буде вести себе так само, як статичний метод. Отже, є обмеження.
Скотт Ленгем

Правда Скотт, але він не дозволяє комусь отримати доступ до цього методу статичним способом, в іншій частині коду. Не те, щоб я міг придумати причину, яку ви хотіли б, але це, здається, те, що просить ОП.
Кріс Марасті-Георг

Ну, якщо ви справді відчули потребу, можете написати це як статичний метод доступу до іншої частини коду, а просто написати нестатичний метод, щоб викликати статичний метод. Я можу помилитися, але я сумніваюся, що це мета запитувача.
Скотт Ленгем

1

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

У мене було безліч класів статичного бізнес-шару, які реалізовували методи CRUD, такі як "Створити", "Прочитати", "Оновити", "Видалити" для кожного типу об'єктів, таких як "Користувач", "Команда", тощо. Потім я створив базу контроль, який мав абстрактне властивість для класу Business Layer, який реалізував методи CRUD. Це дозволило мені автоматизувати операції "Створити", "Прочитати", "Оновити", "Видалити" з базового класу. Мені довелося використовувати Singleton через статичне обмеження.


1

Більшість людей, здається, забувають, що в класах OOP теж є об'єкти, і тому у них є повідомлення, які чомусь c # називають "статичним методом". Те, що існують відмінності між об'єктами екземпляра та об’єктами класу, лише показує недоліки або недоліки в мові. Оптиміст щодо c #, хоча ...


2
Проблема полягає в тому, що це питання не стосується "в OOP". Йдеться про "в С #". У C # "повідомлень" немає.
Джон Сондерс

1

ОК, ось приклад необхідності "типу методу". Я створюю один із наборів класів на основі якогось вихідного XML. Отже, у мене є

  static public bool IsHandled(XElement xml)

функція, яка викликається по черзі на кожному класі.

Ця функція повинна бути статичною, оскільки в іншому випадку ми витрачаємо час на створення невідповідних об'єктів. Як зазначає @Ian Boyde, це можна зробити в заводському класі, але це лише додає складності.

Було б непогано додати його в інтерфейс, щоб змусити виконавців класів реалізувати його. Це не призведе до значних накладних витрат - це лише перевірка часу компіляції / посилання і не впливає на vtable.

Однак це було б і досить незначним поліпшенням. Оскільки метод є статичним, я, як абонент, повинен викликати його явно, і тому отримаю негайну помилку компіляції, якщо вона не реалізована. Якщо дозволити його вказати в інтерфейсі, це означатиме, що ця помилка виникає незначно раніше в циклі розробки, але це тривіально в порівнянні з іншими проблемами з розбитим інтерфейсом.

Тож це незначна потенційна особливість, яку, на баланс, мабуть, краще залишити.


1

Те, що статичний клас реалізується в 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);
}

В даний час я пишу статичні класи, які містять ці методи, без будь-якої перевірки, щоб переконатися, що я нічого не забув. Це як погані старі часи програмування перед OOP.


Це давнє питання; У наші дні ми дуже намагаємось уникати питань, що ґрунтуються на думці, оскільки на них важко авторитетно відповісти. Єдиним хорошим способом відповісти на це було б описати причини, через які вони мали, або, мабуть, мали це зробити так, як вони це зробили.
Натан Туггі

1

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

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


0

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


0

Я думаю, що питання виникає в тому, що C # потребує іншого ключового слова, саме для такої ситуації. Ви хочете, метод, повернене значення якого залежить лише від типу, на який він викликається. Ви не можете назвати це "статичним", якщо вказаний тип невідомий. Але як тільки тип стане відомим, він стане статичним. Ідея "невирішене статичне" - це ще не статично, але як тільки ми дізнаємось тип отримання, він буде. Це ідеально хороша концепція, саме тому програмісти продовжують просити її. Але це не зовсім вписувалося в те, як дизайнери думали про мову.

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

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

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

0

Відповідно до об'єктно-орієнтованого концептуального інтерфейсу, реалізованого класами та має контракт на доступ до цих реалізованих функцій (або методів) за допомогою об'єкта.

Отже, якщо ви хочете отримати доступ до методів контрактування інтерфейсу, ви повинні створити об'єкт. Це завжди необхідно, що це не дозволяється в разі статичних методів. Статичні класи, метод і змінні ніколи не вимагають об'єктів і не завантажуються в пам'ять, не створюючи об'єкт цієї області (або класу), або, можна сказати, не вимагати створення об’єктів.


0

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

Для поточної реалізації мови C # обмеження пов'язане з можливістю успадкування базового класу та інтерфейсів. Якщо "клас SomeBaseClass" реалізує "інтерфейс ISomeInterface" і "клас SomeDerivedClass: SomeBaseClass, ISomeInterface" також реалізує інтерфейс, статичний метод для реалізації методу інтерфейсу не вдасться компілювати, оскільки статичний метод не може мати такий же підпис, як метод екземпляра (який би бути присутнім у базовому класі для реалізації інтерфейсу).

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

Таким чином, це просто зводиться до обмеження конфлікту імен C #, наприклад, та статичних методів одного і того ж імені в успадкуванні. Немає жодних причин, через які C # не можна було "модернізувати" для підтримки контрактів (інтерфейсів) статичних методів.


-1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.