Инициализатор объекта С# хочет использовать неправильный метод добавления

У меня есть следующая иерархия классов:

public class Row : ICloneable, IComparable, IEquatable<Row>,
    IStringIndexable, IDictionary<string, string>,
    ICollection<KeyValuePair<string, string>>,
    IEnumerable<KeyValuePair<string, string>>,
    System.Collections.IEnumerable
{ }

public class SpecificRow : Row, IXmlSerializable,
    System.Collections.IEnumerable
{
    public void Add(KeyValuePair<MyEnum, string> item) { }
}

Однако попытка сделать следующее дает ошибку:

var result = new SpecificRow
    {
        {MyEnum.Value, ""},
        {MyEnum.OtherValue, ""}
    };

Я получаю эту ошибку:

Лучший перегруженный метод добавления «Row.Add(string, string)» для инициализатора коллекции имеет некоторые недопустимые аргументы.

Как я могу сделать так, чтобы использование инициализатора объекта в производном классе SpecificRow допускало тип MyEnum? Похоже, он должен увидеть метод Add в SpecificRow.

Обновление: я реализовал дополнительный интерфейс на SpecificRow, и теперь он выглядит так:

public class SpecificRow : Row, IXmlSerializable,
    System.Collections.IEnumerable,
    ICollection<KeyValuePair<MyEnum, string>>
{ }

Однако я все еще получаю ту же ошибку Add. Я собираюсь попробовать реализовать IDictionary<MyEnum, string> следующим.


person Sarah Vessels    schedule 01.02.2010    source источник
comment
@JonH: неверно, в сообщении об ошибке говорится, что лучшая перегрузка, которую можно найти для данного сайта вызова, — это Добавить (строка, строка), и это не соответствует фактически передаваемым аргументам.   -  person Seth Petry-Johnson    schedule 02.02.2010


Ответы (3)


Инициализатор коллекции не обязательно обращается к какому-либо методу ICollection.Add(x). В частности, для инициализатора коллекции

new SpecificRow {
    { ? }
}

C# просматривает любой метод Add с сигнатурой Add(?); если ? содержит запятые, C# просматривает метод Add с несколькими аргументами. Компилятор вообще не имеет специальной обработки KeyValuePair‹,>. Причина, по которой { string, string } работает, заключается в том, что ваш базовый класс имеет перегрузку Add(string, string), а нет, потому что у него есть перегрузка Add(KeyValuePair<string, string>).

Итак, чтобы поддерживать ваш синтаксис для

new SpecificRow {
    { MyEnum.Value, "" }
};

вам нужна перегрузка формы

void Add(MyEnum key, string value)

Вот и все.

person Ruben    schedule 01.02.2010
comment
Благодарю вас! Я так рад, что мне не нужно реализовывать весь интерфейс только для того, чтобы получить возможности инициализатора объекта. Добавление предложенной вами перегрузки в SpecificRow сработало. - person Sarah Vessels; 02.02.2010

Похоже, это потому, что вы реализуете только IDictionary<string, string> и все остальные связанные с ним интерфейсы. Ваш метод Add(KeyValuePair<MyEnum, string>) не реализует никакого члена интерфейса, это просто еще один член класса SpecificRow, который случайно называется Add, поэтому он игнорируется.

Вы должны быть в состоянии сделать одно из следующего, в зависимости от ваших требований:

  1. Реализуйте IDictionary<MyEnum, string> в дополнение к IDictionary<MyEnum, string>, включая зависимые интерфейсы (ICollection<KeyValuePair<MyEnum, string>> и т. д.).
  2. Реализуйте IDictionary<MyEnum, string> вместо IDictionary<MyEnum, string>, снова включая зависимые интерфейсы.
  3. Измените объявление Row на Row<T> и реализуйте IDictionary<T, string>, включая зависимые интерфейсы. Тогда SpecificRow будет реализовывать Row<MyEnum> вместо Row.
person Daniel Schaffer    schedule 01.02.2010
comment
Очевидно, лучший ответ. Я добавлю, что некоторый рефакторинг класса Row может быть полезен (не слишком ли много реализации интерфейса?) - person Sylvestre Equy; 02.02.2010
comment
Ну, многие из них будут реализованы неявно... IIRC, IDictionary<TKey, TValue> требует ICollection<KeyValuePair<TKey, TValue>>, IEnumerable‹KeyValuePair‹TKey, TValue››` и IEnumerable. Для остальных существует множество сценариев, в которых реализация всех этих интерфейсов была бы разумной, приемлемой и, вероятно, даже желательной. - person Daniel Schaffer; 02.02.2010
comment
Я не уверен, что это правильно. В разделе 7.5.10.3 спецификации языка C# указано, что коллекции должны реализовывать только IEnumerable для инициализации таким образом, и... для каждого указанного элемента по порядку инициализатор коллекции вызывает метод Add для целевого объекта... применяя обычную перегрузку разрешение для каждого вызова. Другими словами, если в коллекции есть метод Add(), который соответствует KeyValuePair‹MyEnum, string›, я ожидаю, что он будет вызван, как и исходный постер. - person Seth Petry-Johnson; 02.02.2010
comment
Сет, она не реализовала какой-либо общий интерфейс, который принимает этот тип. Вот почему он ищет только KeyValuePair<string, string> - person Daniel Schaffer; 02.02.2010
comment
Также стоит отметить, что IEnumerable не имеет метод Add, равно как и ICollection. ICollection<T> делает. - person Daniel Schaffer; 02.02.2010
comment
Я думаю, вы на правильном пути, но реализация ICollection<KeyValuePair<MyEnum, string>> на SpecificRow не решила проблему Add. - person Sarah Vessels; 02.02.2010
comment
Раздел 7.5.10.3 означает, что { x, y } преобразуется в вызов метода Add(x, y). В C# нет специальной поддержки KeyValuePair‹,›, вместо этого вызывается метод IDictionary‹,›.Add(key, value), а не IDictionary‹,›.Add(KeyValuePair‹,›pair) - person Ruben; 02.02.2010
comment
Рубен: Вы совершенно правы. У меня был полный момент duh, и я только что отредактировал свой ответ, чтобы отразить тот факт, что проблема заключается в реализации IDictionary, а не ICollection. - person Daniel Schaffer; 02.02.2010
comment
@Daniel - я думаю, что Сет прав в том, что вам нужно реализовать только IEnumerable<T> вместо ICollection<T>, поскольку компилятор не использует интерфейс для инициализации коллекции. Однако ОП реализовал только IEnumerable. - person Lee; 02.02.2010
comment
@Daniel: я понимаю, о чем вы говорите, но я понимаю инициализаторы коллекций так, что каждый элемент в списке указывает набор аргументов для передачи методу Add() инициализируемого типа коллекции. Поскольку она явно строит SpecificRow, я ожидаю, что разрешение перегрузки скажет, какая перегрузка SpecificRow.Add() принимает KVP‹enum, string›, а затем разрешит правильный метод. Инициализаторы коллекций требуют, чтобы инициализированный класс реализовывал неуниверсальный IEnumerable, зачем ей реализовывать универсальный интерфейс? - person Seth Petry-Johnson; 02.02.2010
comment
@Daniel: это тоже не реализация IDictionary. Это любой общедоступный метод Add, будь то из IDictionary или только один из ваших собственных. Точно так же, как foreach вызывает общедоступный GetEnumerator, независимо от того, реализует ли он IEnumerable или нет. Единственным требованием для обоих является то, что класс каким-то образом реализует IEnumerable, но сама реализация в основном игнорируется в пользу некоторых общедоступных методов с хорошо названными именами. - person Ruben; 02.02.2010
comment
Ли: Вы не можете добавить к перечисляемому... перечисляемый, как следует из названия, предназначен только для перечисления, и, как я упоминал в предыдущем комментарии, ни необщий интерфейс IEnumerable, ни общий интерфейс IEnumerable<T> не имеют Add метод... так как это будет работать? - person Daniel Schaffer; 02.02.2010
comment
@ Рубен и Даниэль: у меня только что был свой собственный момент. Если бы инициализатор коллекции был написан как { new KeyValuePair‹T,V› { Enum.Foo, bar } } , он, вероятно, сработал бы, верно? Потому что тогда был бы выбран метод SpecificRow.Add(), а не метод Add словаря. Я не получал столько удовольствия уже несколько дней!! - person Seth Petry-Johnson; 02.02.2010
comment
@Сет: верно. весь материал IEnumerable/ICollection/IDictionary сбивает вас с толку. Единственное, что вам нужно, это метод Add с нужным количеством (и типом) аргументов, а в данном случае и Add с двумя аргументами. - person Ruben; 02.02.2010
comment
@Ruben ... вау, я совершенно не понимал, что вы можете реализовать GetEnumerator и заставить его работать без реализации IEnumerable. Мне это кажется немного запутанным - я не уверен, почему было бы желательно реализовать GetEnumerator без IEnumerable. Так что, похоже, ей все-таки нужен метод Add(MyEnum, string). - person Daniel Schaffer; 02.02.2010
comment
@Daniel: причина, по которой foreach смотрит на общедоступный GetEnumerator, заключается в том, что вы можете вернуть специализированный перечислитель (предварительно универсальные, взгляните на IDictionary) или перечислитель структур (List‹T›), а не простой IEnumerator(‹T› ). Это также устраняет неоднозначность при реализации нескольких IEnumerable‹T›. - person Ruben; 02.02.2010
comment
@Daniel - Потому что, как я уже сказал, компилятор не использует интерфейс для добавления в коллекцию - он просто проверяет, реализует ли он IEnumerable<T> и имеет метод Add(T item). Затем он использует этот метод Add для добавления в коллекцию. - person Lee; 02.02.2010
comment
Аргумент до появления дженериков, который я могу понять, кроме того, почему кто-то должен делать это сейчас, мне не по плечу. Для реализации нескольких IEnumerable<T> у каждого из них будет своя собственная подпись (то есть, если вы возвращаете IEnumerator<T>), поэтому не будет никакой двусмысленности, или вы можете явно реализовать один или несколько из них. - person Daniel Schaffer; 02.02.2010
comment
Боюсь, тут много неясностей. что бы foreach(var e in c) означало? Какой тип var будет для Dictionary‹K,V›? KeyValuePair‹K,V›, DictionaryEntry или объект? (IDictionary‹K,V› : IDictionary : IEnumerable) - person Ruben; 02.02.2010
comment
FWIW, именно поэтому я ТАК обожаю. Отличное обсуждение! - person Daniel Schaffer; 02.02.2010
comment
ИМХО, существует очень ограниченное количество сценариев, в которых реализация нескольких общих определений IEnumerable<T> имела бы смысл. В любом из них мне кажется, что при работе с экземпляром в ситуации, когда вам нужно перечислить с использованием одного из этих интерфейсов, наилучшей практикой было бы ссылаться на приведение экземпляра как на интерфейс, а не на конкретный тип, что было бы устранить любую двусмысленность. Использование public вместо явных реализаций или реализаций, не являющихся частью интерфейса, кажется, только усугубит путаницу или двусмысленность. Мы говорим одно и то же? - person Daniel Schaffer; 02.02.2010
comment
Взгляните на эту статью Мэдса Торгерсена, где содержится интересная справочная информация и обоснование требования метода IEnumerable/Add: blogs.msdn.com/madst/archive/2006/10/10/ - person LukeH; 02.02.2010
comment
@Luke: Интересное чтение, хотя оно только укрепляет мое мнение о том, что не реализация соответствующих универсальных интерфейсов — это ууууууууууууууууууууууууууууууууууууууууу по её идее, и что возможность иметь эту функциональность без интерфейсов на самом деле просто лайфхак, предназначенный для пре-дженериков. типы каркаса. - person Daniel Schaffer; 02.02.2010

Ответ Рубена, безусловно, лучший, но если вы не хотите добавлять Add(MyEnum key, string value), вы также можете инициализировать коллекцию следующим образом:

var result = new SpecificRow
{
    new KeyValuePair<MyEnum, string>(MyEnum.Value, ""}),
    new KeyValuePair<MyEnum, string>(MyEnum.OtherValue, ""})
};
person Seth Petry-Johnson    schedule 01.02.2010