2014-09-17 2 views
4

Если название не имело смысла, вот пример:Как создать ковариантный метод расширения на общем интерфейсе в C#?

interface IEat { void Eat; } 
class Cat : IEat { void Eat { nom(); } } 
class Dog : IEat { void Eat { nom(); nom();nom(); } } 
class Labrador : Dog { } 

Я хотел бы создать расширение метод, как это:

public static void FeedAll(this IEnumerable<out IEat> hungryAnimals) { 
    foreach(var animal in hungryAnimals) animal.Eat(); 
} 

Так что я могу сделать это:

listOfCats.FeedAll(); 
listOfLabs.FeedAll(); 
listOfMixedHungryAnimals.FeedAll(); 

Возможно ли это? Где я неправ?

Приложение реального мира здесь - это то, что «Собака» является основным базовым классом в моем приложении, и есть много подклассов, каждый из которых может время от времени иметь ИЛИС вещей, которые должны выполнять групповые операции на их. Чтобы их просто называть методом расширения в списке интерфейса, который они все реализовали, будет субоптимальным.

Edit:

Я goofed в пример немного. Мой фактический код использует IList, я надеялся, что доступны операции Count и index. Основываясь на ответах ниже, я предполагаю, что мне придется пойти в другом направлении для методов, которые требуют IList-семантики.

+0

Не работает ли это без ключевого слова 'out'? 'IEnumerable ' уже является 'IEnumerable ' из-за 'IEnumerable ', являющегося ковариантным. –

+0

Стриланк, ты прав, я немного изменил свой вопрос. Хотел сделать это с IList, по-видимому, это невозможно (я понимаю, почему IList не ковариант, я просто надеялся, что это было обходным путем, тем более, что мне не нужно Add.) – richardtallent

+0

В большинстве списков реализовано 'IReadOnlyList ' (к сожалению он не может быть помещен выше 'IList ', так что это может сработать. –

ответ

3

IEnumerable уже является ковариантным, поэтому ваш метод расширения может принимать IEnumerable<IEat>, а экземпляр IEnumerable<Dog> будет действительным аргументом, что делает метод расширения применимым к переменным этих типов.

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

+0

В моем фактическом коде используется IList, и я подозревал, что это может быть проблемой. Отредактировал вопрос, чтобы сделать его более понятным. – richardtallent

+1

@richardtallent И 'IList' не ковариант, он инвариантен. Как я уже сказал, нет ничего, что можно сделать, чтобы инвариантный параметр действовал ковариантно; вам просто нужно использовать ковариантный тип в качестве параметра. – Servy

+0

Это может послужить стимулом для перехода на .NET 4.5, поскольку IList реализует IReadOnlyList , который, я считаю, обойдется в этом, нет? – richardtallent

3

Это будет работать, если вы удалите out:

public static void FeedAll(this IEnumerable<IEat> hungryAnimals) { 
    foreach(var animal in hungryAnimals) animal.Eat(); 
} 

дисперсия относится к параметрам самого интерфейса (T в IEnumerable<T> в данном случае), поэтому List<Dog> совместим с IEnumerable<IEat>.

-3

Вам не нужно, чтобы оно передавалось по ссылке, поэтому оно будет работать без него.

+0

Параметр не передается по ссылке, поэтому он не работает. – Servy

+0

Прочтите ссылку в разделе, в котором говорится «Передача по ссылке против передачи по значению». http://msdn.microsoft.com/en-us/library/ms173114.aspx –

+2

Что относительно этой статьи вы имеете в виду? Я знаю, в чем разница. Если вы считаете, что этот метод передает параметр по ссылке, то * вы * должны прочитать это снова, потому что этого здесь вообще не происходит. Поскольку использование 'out' или' ref' не используется, параметр передается по значению. – Servy

0

Как отмечает Servy, Enumerable<T> уже ковариант, но это не обязательно. Для более старых версий, где интерфейс не является ковариантным, вы можете сделать это:

public static void FeedAll<T>(this IEnumerable<T> hungryAnimals) where T : IEat 
{ 
    foreach(var animal in hungryAnimals) animal.Eat(); 
} 
+0

Это работает, пока вам не понадобятся перегрузки. – hangar

+0

@hangar Что значит? Разрешение перегрузки также будет обрабатывать перегрузки с построенными типовыми типами. Однако вы не можете назвать перегрузку, которую, как вы думаете, вы звоните; это оно? – phoog

+0

Нет, C# не перегружает общие ограничения. См. [Объяснение Эрика Липперта] (https://blogs.msdn.microsoft.com/ericlippert/2009/12/10/constraints-are-not-part-of-the-signature/). – hangar

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