2014-01-05 3 views
2

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

В настоящее время я изучаю перечисления, IEnumerable и т. Д., И это меня смутило. Вот пример того, что я имею в виду:

static IEnumerable<int> Fibs(int fibCount) 
{ 
    for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++) { 
     yield return prevFib; 
     int newFib = prevFib + curFib; 
     prevFib = curFib; 
     curFib = newFib; 
    } 
} 

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

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

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

public IEnumerable<Category> Categories {get;set;} 
+5

'yield return' is magic. Он возвращает 'IEnumerable' типа после оператора' return'. – Travis

+0

Вот несколько вопросов по теме: http://stackoverflow.com/questions/317462/some-help-understanding-yield http://stackoverflow.com/questions/288808/is-yield-return-ienumerable-ienumerator http : //stackoverflow.com/questions/39476/what-is-the-yield-keyword-used-for-in-c Классы 'Task' /' Task 'также имеют специальную поддержку компилятора, так что' async'/' await' может быть реализована путем создания (скрытого) конечного автомата, который очень похож на тот, который создан для ключевого слова 'yield'. – spender

+2

@Andrew B: ваш второй пример - свойство с типом интерфейса, полностью отличается от вашего первого. Вы можете использовать интерфейсы как свойства, параметры, типы возвращаемых данных и т. Д., Когда захотите, до тех пор, пока вы переходите к * заданию * свойства или * возвращаете * значение, у вас есть конкретная реализация для работы. 'yield' является особенным, потому что он создает для вас конкретную реализацию. –

ответ

2

IEnumerable - особый случай. Оператор return yield инструктирует компилятор добавить код, реализующий IEnumerable.
Что касается ваших редактирований:
Если интерфейс используется типа собственности, любой объект класса, который реализует этот интерфейс может быть назначен и свойство возвращает объект, который реализует этот интерфейс. В вашем примере любой объект категорий, который реализует IEnumerable<Category>, может быть присвоен свойству, например. a List<Category>. По сравнению с использованием только List<Category>, использование интерфейса позволяет назначать более широкий диапазон объектов. Интерфейс определяет абстрактные требования, которые имеют отношение к свойству.

6

Существует дополнительная магия, когда вы используете yield keyword, то есть создать блок итератора. Компилятор делает для вас конечный автомат.

Итак, у C# есть специальная функция, и у этой функции есть только IEnumerable<>. Так что это язык C#, который является волшебным.

Интерфейс IEnumerable<> сам по себе является скучным обычным типом. В этом нет волшебства.

Примечание: Технически yield магия работает, когда «формальный» тип возвращаемого значения либо IEnumerable<>, IEnumerator<>, IEnumerable или IEnumerator, но, как правило, используется первый из них. Разумеется, не ходите без родовых.

+0

Ну, я видел, что это часто используется в MVC, чтобы передавать списки с IEnumerable и IList и т. Д., Которые определенно не имели никакого отношения к ключевому слову yield ... Например, как свойство в модели? –

+1

@AndrewB: откуда появились эти интерфейсы «IEnumerable»? Из объектов, которые реализуют «IEnumerable», наверняка. Свойство Model просто устанавливается в контроллере (вы, разработчик) ... источником может быть метод с возвратом доходности или объект, который реализует IEnumerable ... например, те, которые возвращаются из операторов linq, например. – spender

+0

@spender, Хм ... Так что я должен рассматривать эти 2 раза «подстановочные знаки»? Ключевое слово yield и несколько раз я использую IEnumberable, IList и т. Д. В шаблоне MVC? –

0

В C#, интерфейсы представляют собой особый «вид» типа, который отличается от класса в нескольких ключевых направлениях:

  • Вы не можете включать любой код реализации их методов.
  • Один класс может «наследовать» (называемый «реализация») столько, сколько он хочет.
  • Вы не можете создать экземпляр интерфейса new.

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

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

Хитрость заключается в том, если вы определяете свойство, как, скажем, IEnumerable<int>, и вы хотите установить это значение, вы не можете сделать это:

public IEnumerable<int> Numbers { get; set; } 
... 
this.Numbers = new IEnumerable<int>(); 

Это ошибка. Вы не можете создать новый экземпляр интерфейса, потому что это всего лишь «шаблон» - нет ничего «позади», чтобы на самом деле что-либо делать. Тем не менее, вы можете сделать это:

public IEnumerable<int> Numbers { get; set; } 
... 
this.Numbers = new List<int>(); 

Поскольку List<T> реализует IEnumerable<T>, компилятор будет автоматически выполнять преобразование типа, чтобы сделать работу присваивания. Любой конкретный класс, который реализует IEnumerable<>, может быть присвоен свойству типа IEnumerable<>, поэтому вы часто видите свойства интерфейса интерфейса. Он позволяет вам изменить базовый тип бетона (возможно, вы хотите изменить List<T> на ObservableCollection<T>, но пользователи вашего класса не знают и не заботятся о том, когда вы это делаете.

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

public IEnumerable<string> GetName() 
{ 
    // this fails. 
    return new IEnumerable<string>(); 

    // this works. 
    return new List<String>(); 

    // this also works because magic!~ 
    yield return "hello"; 
    yield return "there"; 
    yield return "!"; 
} 

Этот последний случай является особой формой «синтаксический сахар», что C# обеспечивает, потому что это такое общее требование, как и другие люди упоминали, компилятор специально. ищет yield return заявления о методах, возвращающих IEnumerable или IEnumerator (как общие, так и не общие версии) и делает некоторую здоровенную перекодировку кода.

За кулисами C# создает скрытый класс, который реализует IEnumerable<string> и реализации его GetEnumerator способ вернуть IEnumerator<string> объект, который предоставляет эти три строковые значения. Для вас было бы очень много шаблонов для написания, хотя вы, безусловно, могли бы написать это сами. В предыдущих версиях C# не было yield, и вам пришлось написать его самостоятельно.

Если вы действительно хотите знать, вы можете найти эквивалент C# here, среди других мест. По сути, он принимает метод, который содержит ваши операторы yield и создает из него , но превращает его в конечный автомат. Он использует эквивалент меток и операторов goto для возврата в нужное место каждый раз, когда потребитель вызывает MoveNext в том же экземпляре. Кроме того, как я понимаю, он делает то, что вы на самом деле не можете делать на C# (он перескакивает и выходит из циклов), но это законно в IL-коде, поэтому его реализация более эффективна, чем вы могли бы написать сами.

Но как только вы пройдете секретную причину ключевого слова yield, вы все равно будете делать то же самое. Вы все еще создаете класс, который реализует IEnumerable<>, и используя это как возвращаемое значение для вашего метода.

0

Для вашего конкретного примера, хотя тип возвращаемого метода равен IEnumerable<int>, фактический тип возврата будет типом, который реализует IEnumerable<int>. Как отмечали другие, «доходность доходности» приводит к типу, который реализует IEnumerable<int>. В этом конкретном случае вы не знаете, что это за тип.Когда я запускаю это через отладчик и делаю result.GetType() (где result возвращается из метода Fibs), я вижу, что result имеет тип <Fibs>d__0, что звучит немного странно для меня. Поэтому вместо того, чтобы беспокоиться об этом странном звучании, мы можем просто рассматривать его как IEnumerable<int>, потому что все, что мы действительно хотим сделать, это перебрать его, что является поведением, которое предоставляет IEnumerable<int>.

Это для вашего конкретного примера, но идея такая же в другом месте. Используя интерфейс, вы говорите, что вам все равно, какой тип используется/возвращался, если он раскрывает определенное поведение или свойства. Например, если у меня есть интерфейс IFoo, который предоставляет метод DoSomething(), и у меня есть метод, который возвращает IFoo, тогда я могу вернуть все, что реализует IFoo, но независимо от того, что я возвращаю, вызывающий метод уверен, что он может DoSomething() с объектом. Точно так же, если у меня есть метод, который принимает IFoo, тогда метод уверен, что он сможет DoSomething() с этим параметром.

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

И все-таки есть другие причины использовать интерфейсы, например, для создания макетов для тестирования, для инъекций зависимостей, для плавного проектирования API и, вероятно, из сотни других причин.

Смежные вопросы