Почему интерфейсы C # не могут содержать поля? - PullRequest
209 голосов
/ 22 января 2010

Например, предположим, что я хочу интерфейс ICar и что все реализации будут содержать поле Year. Означает ли это, что каждая реализация должна отдельно объявлять Year? Разве не было бы лучше просто определить это в интерфейсе?

Ответы [ 11 ]

228 голосов
/ 22 января 2010

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

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

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

класс говорит: «когда вы создаете мой экземпляр, вставьте ссылку на Foo.M в слот для IFoo.M.

Тогда, когда вы делаете звонок:

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

компилятор генерирует код, который говорит: «спросите у объекта, какой метод находится в слоте для IFoo.M, и вызовите этот метод.

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

128 голосов
/ 22 января 2010

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

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

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

Реализующие классы могут использовать авто-свойства для упрощения реализации, если с этим свойством не связана специальная логика:

class Automobile : ICar
{
    public int Year { get; set; } // automatically implemented
}
48 голосов
/ 22 января 2010

Объявите это как свойство:

interface ICar {
   int Year { get; set; }
}
35 голосов
/ 22 января 2010

Эрик Липперт прибил его, я буду использовать другой способ сказать, что он сказал. Все члены интерфейса являются виртуальными, и все они должны быть переопределены классом, который наследует интерфейс. Вы не пишете явно ключевое слово virtual в объявлении интерфейса и не используете ключевое слово override в классе, они подразумеваются.

Виртуальное ключевое слово реализовано в .NET с методами и так называемой v-таблицей, массивом указателей на методы. Ключевое слово override заполняет слот v-таблицы другим указателем метода, перезаписывая тот, который создается базовым классом. Свойства, события и индексаторы реализованы как методы под капотом. Но поля нет. Поэтому интерфейсы могут не содержать полей.

19 голосов
/ 22 января 2010

Почему бы просто не иметь свойства Year, которое прекрасно?

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

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

17 голосов
/ 22 января 2010

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

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

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; }
    }
}
6 голосов
/ 24 октября 2014

Другие задали «Почему», поэтому я просто добавлю, что ваш интерфейс может определять элемент управления; если вы завернете его в свойство:

public interface IView {
    Control Year { get; }
}


public Form : IView {
    public Control Year { get { return uxYear; } } //numeric text box or whatever
}
3 голосов
/ 22 января 2010

Интерфейсы не содержат никакой реализации.

  1. Определение интерфейса со свойством.
  2. Далее вы можете реализовать этот интерфейс в любом классе и использовать этот класс в будущем.
  3. При необходимости вы можете определить это свойство как виртуальное в классе, чтобы вы могли изменить его поведение.
2 голосов
/ 02 июня 2016

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

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

0 голосов
/ 22 января 2010

Интерфейс определяет публичные свойства и методы экземпляра. Поля, как правило, являются частными или имеют наиболее защищенный, внутренний или защищенный внутренний тип (термин «поле» обычно не используется ни для каких общедоступных).

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...