Чому інтерфейси C # не можуть містити поля?


223

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


21
Інтерфейси не мають реалізації, для цього використовують абстрактний клас із властивістю Рік
PostMan

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

5
Але якщо поле є загальнодоступним, це частина договору, а не лише деталь виконання, правда?
Олексій

Відповіді:


238

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

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

interface IFoo { void M(); } 
class Foo : IFoo { public void M() { ... } }

клас говорить "коли ви створюєте екземпляр мене, введіть посилання на Foo.M у слот для IFoo.M.

Потім, коли ви телефонуєте:

IFoo ifoo = new Foo();
ifoo.M();

компілятор генерує код, який говорить "запитайте об'єкт, який метод є в слоті для IFoo.M, і викликайте цей метод.

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


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

2
Мені подобається пояснення простими словами. Дякую. "CLR через C #" та "Essential .net volume 1" містять більше деталей.
Sandeep GB

6
Чому поле не має слота? і те саме питання з операторами? Я пам’ятаю, як слухав про введення качок за допомогою відображення, щоб побачити, чи реалізований інтерфейс, навіть якщо клас не успадкував інтерфейс. Чому нахил відображення (або слот) використовується для підтягування поля? Я все ще пишу свій код, тому мені, можливо, не потрібні / хочу поля, але я був здивований, виявивши, що не можу використовувати операторів. Оператори так само, як на мене, розуміють методи, за винятком того, що не всі можуть бути перевантажені ( An interface cannot contain constants, fields, operators. З msdn.microsoft.com/en-us/library/ms173156.aspx )

@acidzombie: Питання, чому інтерфейс не може визначити операторів, відрізняється (хоча, можливо, пов'язано) з тим, чому він не може містити поля; Я б запропонував написати ще одне питання, якщо ви все ще зацікавлені в цьому.
Адам Робінсон

1
@ b1nary.atr0phy чому з полями? Наприклад, якщо я оголосив метод int One(), то реалізація public int One(){return 1;}не є полем.
Привіт-Ангел

131

Інтерфейси в C # призначені для визначення договору, якого буде дотримуватися клас - а не конкретної реалізації.

У цьому дусі інтерфейси C # дозволяють визначити властивості - які абонент повинен надати реалізацію для:

interface ICar
{
    int Year { get; set; }
}

Класи реалізації можуть використовувати автоматичні властивості для спрощення реалізації, якщо немає спеціальної логіки, пов'язаної з властивістю:

class Automobile : ICar
{
    public int Year { get; set; } // automatically implemented
}

5
Чи не все, що є публічним, є частиною договору. Якщо клас має загальнодоступний int Year, чи не говорить про те, що у контракті класу є поле типу Year, яке має бути присутнім на ньому, і доступне?
Дідьє А.

1
Пізно до партії, але ні, в цьому випадку це означає, що у контракту є рік ВЛАСНІСТЬ, який повинен застосовувати будь-який дотриманий клас. Властивості - це фактично методи get / set, у яких автоматично створюється резервне поле, якщо спеціальна логіка не потрібна. Спеціальний синтаксис - лише для чіткішого позначення.
користувач3613916

Як я можу визначити постійне значення за замовчуванням (наприклад, 123) для цієї автоматичної реалізації Year?
lama12345

1
@ lama12345 Я також спізнююся на вечірку, але оскільки C # 6 (2015, .NET Framework 4.6 та .NET Core) ви можете використовувати для цього власність авто. public int Year => 123;. Однак у цьому випадку не має сенсу мати сеттер, тому інтерфейс повинен був би бути визначений зint Year { get; }
EriF89

56

Декларуйте це як властивість:

interface ICar {
   int Year { get; set; }
}

47
Питання " Чому інтерфейси C # не можуть містити поля?". Це не вирішує цього питання.
AakashM

14
Я згоден, що ця відповідь не відповідає на питання ОП, але добре, це вирішило мою проблему.
Горген

36

Ерік Ліпперт прибив це, я буду використовувати інший спосіб сказати те, що він сказав. Усі члени інтерфейсу є віртуальними, і всі вони повинні бути замінені класом, який успадковує інтерфейс. Ви не чітко пишете віртуальне ключове слово в декларації інтерфейсу, а також не використовуєте ключове слово override у класі, вони маються на увазі.

Віртуальне ключове слово реалізовано в .NET з методами та так званою v-таблицею, масивом покажчиків методів. Ключове слово override заповнює слот v-table іншим вказівником методу, замінюючи той, створений базовим класом. Властивості, події та індексатори реалізуються як методи під капотом. Але поля - ні. Отже, інтерфейси не можуть містити поля.


Ви надали технічну / фактичну назву (v-table) до концепції слотів, згаданих Еріком. Дякую за деталі, Ганс.
RBT

Який би зміст v-таблиці, якщо інтерфейси заборонені за замовчуванням? Це змінюється в C # 8.0, але це не в курсі.
AnthonyMonterrosa

19

Чому б просто не мати Yearвластивість, яка цілком чудово?

Інтерфейси не містять полів, оскільки поля представляють конкретну реалізацію подання даних, і їх викриття порушить інкапсуляцію. Таким чином, наявність інтерфейсу з полем ефективно кодує реалізацію замість інтерфейсу, що є цікавим парадоксом для інтерфейсу!

Наприклад, частина вашої Yearспецифікації може зажадати , щоб він був інвалідом ICarвиконавцями , щоб віднесення до , Yearяка пізніше поточного року + 1 або до 1900. Там немає ніякого способу , щоб сказати , що якби відкриті Yearполя - набагато краще використовувати властивості замість цього виконувати роботу.


18

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

Розгляньте цей сценарій, використовуючи запропоновані вами дії:

public interface InterfaceOne
{
    int myBackingVariable;

    int MyProperty { get { return myBackingVariable; } }
}

public interface InterfaceTwo
{
    int myBackingVariable;

    int MyProperty { get { return myBackingVariable; } }
}

public class MyClass : InterfaceOne, InterfaceTwo { }

Тут у нас є кілька проблем:

  • Оскільки всі члени інтерфейсу - за визначенням - загальнодоступні, наша змінна резервна копія тепер піддається всім, хто використовує інтерфейс
  • Який myBackingVariableбуде MyClassвикористовувати?

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

public interface IMyInterface
{
    int MyProperty { get; set; }
}

public abstract class MyInterfaceBase : IMyInterface
{
    int myProperty;

    public int MyProperty
    {
        get { return myProperty; }
        set { myProperty = value; }
    }
}

7

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

public interface IView {
    Control Year { get; }
}


public Form : IView {
    public Control Year { get { return uxYear; } } //numeric text box or whatever
}

3

Інтерфейси не містять жодної реалізації.

  1. Визначте інтерфейс із властивістю.
  2. Далі ви можете реалізувати цей інтерфейс у будь-якому класі та використовувати цей клас уперед.
  3. Якщо потрібно, ви можете визначити це властивість у класі як віртуальне, щоб ви могли змінити його поведінку.

3

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

Ви можете заперечити, що тоді чому дозволені властивості? Тож проста відповідь - властивості внутрішньо визначаються лише як методи.


1
Якщо вам потрібно отримати доступ до члена, просто зробіть його власністю, і ви будете добрі.
Unome

0

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


0

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

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

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

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