C # Ковариантность и Контравариантность при реализации интерфейсов - PullRequest
9 голосов
/ 14 августа 2011

Я недавно решил освежить свою память об основах C #, так что это может быть тривиально, но я столкнулся со следующей проблемой:

StringCollection использовался в .NET v1.0 для создания строго типизированной коллекции для строк, в отличие от ArrayList на основе object (позже это было улучшено за счет включения общих коллекций):

Взглянув на определение StringCollection, вы увидите следующее:

// Summary:
//     Represents a collection of strings.
[Serializable]
public class StringCollection : IList, ICollection, IEnumerable
{
...
    public int Add(string value);
...
}

Вы можете видеть, что он реализует IList, который содержит следующее объявление (среди нескольких других объявлений):

int Add(object value);

Но не:

int Add(string value);

Мое первое предположение состояло в том, что это возможно благодаря правилам ковариации .NET Framework.

Итак, просто чтобы убедиться, я попытался написать свой собственный класс, который реализует IList и изменил

int Add(object value);

для получения строкового типа вместо типа объекта, но, к моему удивлению, при попытке скомпилировать проект я получил ошибку времени компиляции:

does not implement interface member 'System.Collections.IList.Add(object)'

Есть идеи, что вызывает это?

Спасибо!

Ответы [ 4 ]

8 голосов
/ 14 августа 2011

Поведение вызвано явной реализацией IList.Add(object), а не со / контравариантностью. Согласно документации MSDN, StringCollection явно реализует IList.Add(object); Add(string) метод не связан. Реализация может выглядеть примерно так:

class StringCollection : IList
{
    ...
    public int Add(string value)
    {} // implementation

    public int IList.Add (object value)
    {
        if (!value is string)) return -1;
        return Add(value as string)
    }
}

Это различие можно наблюдать:

  StringCollection collection = new StringCollection();
  collection.Add(1); // compile error
  (collection as IList).Add(1); // compiles, runtime error
  (collection as IList).Add((object)"") // calls interface method, which adds string to collection

Добавление

Выше не рассматривается, почему этот шаблон реализован. Спецификация языка C # гласит, что [§13.4.1, акцент добавлен]:

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

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

StringCollection придерживается требуемого поведения IList - IList не гарантирует, что любой произвольный объект может быть добавлен к нему. StringCollection дает более сильные гарантии - прежде всего, то, что он будет содержать только строки. Класс включает в себя собственные строго типизированные методы для Add, Contains, Item и другие для стандартного варианта использования, где к нему обращаются как StringCollection, а не IList. Но он по-прежнему прекрасно работает как IList, принимая и возвращая объекты, но возвращая код ошибки (как позволяет IList), если делается попытка добавить элемент, который не является строкой.

В конечном счете, обнаружение интерфейса в классе (т. Е. Явно ли оно реализовано) остается на усмотрение автора класса. В случае классов инфраструктуры явные реализации включены в документацию MSDN, но недоступны как члены класса (например, показаны в контекстах автозаполнения).

0 голосов
/ 14 августа 2011

IList.Add(object) может принимать параметры, отличные от строк - он может принимать любой тип.Поэтому, если вы объявите, что ваша реализация интерфейса принимает только строки, она больше не соответствует спецификации интерфейса, потому что теперь я не могу передать Stream, например.

Дисперсия может работать Другой способ : если метод интерфейса был объявлен для приема строк, то принятие объектов будет в порядке, поскольку строки также являются объектами, и поэтому любой ввод в метод интерфейса также будет приемлемым вводом для вашей реализации.(Однако вам все равно придется предоставить явную реализацию интерфейса с методом, принимающим строку, потому что в C # реализация метода интерфейса очень точно соответствует объявлению метода интерфейса.)

0 голосов
/ 14 августа 2011

То, что IList в основном указывает на то, что вы можете вызвать Add и передать любой объект в качестве параметра от Int в штучной упаковке к другому IList к System.DivideByZeroException.Если вы предоставляете только метод Add( string ), вы не выполнили это требование, поскольку можете добавлять только строки.

Другими словами, вы не сможете вызвать StringCollection.Add( new Object() );, что должно быть идеальновыполнимо, если интерфейс был реализован должным образом.: D

0 голосов
/ 14 августа 2011

Если вы используете .net 2.0+, я бы просто использовал дженерики:

IList<string> list = new List<string>();

Это должно дать вам все, что вы хотите.

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