Только в состоянии вызывать Cast и OfType на IEnumerable - PullRequest
0 голосов
/ 02 июля 2019

В библиотеке, которую я использую, у меня возникают проблемы с вызовом любых методов LINQ, кроме тех, что в IEnumerable. У меня есть иерархия классов следующим образом (наименование немного странное, поскольку оно скрыто из внутреннего кода)

Item : GeneralObject

ItemCollection : GenericCollection<ItemCollection, Item>

GenericCollection<TCollection, TItem> : GeneralObjectCollection, IEnumerable<TItem>
    where TCollection : GenericCollection<TCollection, TItem>
    where TItem : GeneralObject

GeneralObjectCollection : ICollection, IEnumerable<GeneralObject>

Как видно, IEnumerable находится в иерархии классов ItemCollection дважды, поэтому есть два метода GetEnumerator, один из которых обеспечивает GeneralObject, а другой - Item.

Когда я смотрю на определение класса, предоставленное через метаданные в VS2019, каждый класс, который реализует IEnumerable<TItem>, также показывает, как реализован IEnumerable.

При такой настройке я не могу вызывать большинство методов LINQ, таких как Select, Where и т. Д., В любых экземплярах ItemCollection и могу выполнять их только в IEnumerable. Похоже, что он должен явно поддерживать методы на IEnumerable<T>, но по какой-то причине я должен сначала привести его к действию.

Как приведение к IEnumerable<TItem>, так и использование .Cast<TItem> работают, но, похоже, они не нужны.

Пример кода ниже. Второй пример не будет компилироваться:

private ItemCollection GetItemsFromDatabase(string query)
{
    // Internal logic.
}

List<Item> newItemList = ((IEnumerable<Item>)GetItemsFromDatabase(itemQuery))
                                        .Select(x => new ItemInfo(x.Name, x.Id, x.Guid)).ToList();

и

private ItemCollection GetItemsFromDatabase(string query)
{
    // Internal logic.
}

List<Item> newItemList = GetItemsFromDatabase(itemQuery).Select(x => new ItemInfo(x.Name, x.Id, x.Guid)).ToList();

Ошибка: «ItemCollection» не содержит определения «Select», и нет доступного метода расширения «Select», принимающего первый аргумент типа «ItemCollection» (вы пропустили директиву using или ссылку на сборку?).

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

Ответы [ 2 ]

3 голосов
/ 02 июля 2019

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

class Ark : IEnumerable<Turtle>, IEnumerable<Giraffe> 
{ ... }

, а затем вы говорите

Ark ark = whatever;
ark.Select(x => whatever);

, то каким-то образом компилятор должен знать, имеете ли вы в виду a.Select<Turtle, Result> или a.Select<Giraffe, Result>.Ни при каких обстоятельствах C # не будет пытаться угадать, что вы имели в виду a.Select<Animal, Result> или a.Select<object, Result>, потому что это не был один из предложенных вариантов .C # делает выбор только из доступных типов, а доступны следующие типы: IEnumerable<Turtle> и IEnumerable<Giraffe>.

Если нет оснований для принятия решения, вывод типа не выполняется.Так как мы получили доступ к методам расширения только после того, как все другие попытки разрешения перегрузки потерпели неудачу, мы, вероятно, потерпим неудачу при разрешении перегрузки на этом этапе.C # намек на то, что вы имели в виду.Самый простой способ это

ark.Select<Turtle, Result>(x => whatever);

Но вы также можете сделать

ark.Select((Turtle x) => whatever);

Или

((IEnumerable<Turtle>)ark).Select(x => whatever);

Или

(ark as IEnumerable<Turtle>).Select(x => whatever);

Это всехорошо.Вы пришли к выводу, что это компилируется:

ark.Cast<Turtle>().Select(x => whatever); 
// NEVER DO THIS IN THIS SCENARIO
// USE ANY OF THE OTHER TECHNIQUES, NEVER THIS ONE

Вы понимаете, почему это опасно ?Не продолжайте, пока не поймете, почему это, вероятно, неправильно.Обоснуйте это.


В общем, опасно применять тип, который реализует два "одинаковых" универсальных интерфейса, потому что могут произойти очень странные вещи .Язык и среда выполнения не были предназначены для элегантной обработки такого рода объединения.Рассмотрим, например, как работает ковариация;что произойдет, если мы приведем ark к IEnumerable<Animal>?Посмотри, сможешь ли ты понять это;затем попробуйте и посмотрите, были ли вы правы.

К сожалению, вы находитесь в еще худшем положении;что если вы создадите экземпляр GenericCollection<TCollection, TItem>, такой, что TItem будет GeneralObject? Теперь вы реализовали IEnumerable<GeneralObject> дважды! Это действительно сбивает с толку пользователей, и CLR это совсем не нравится.

Лучше сделать так, чтобы Ark не реализовывал ни интерфейс, носкорее, выставьте два метода, один из которых возвращает черепах, а второй - жирафов.Вы должны настоятельно рассмотреть возможность сделать то же самое в вашем классе.Лучшим дизайном было бы сделать GenericCollection<TCollection, TItem> не реализующим IEnumerable<TItem>, а иметь свойство IEnumerable<TItem> Items { get { ... } }.

С этим дизайном вы можете затем сделать collection.Select, чтобы получить общие объекты, или collection.Items.Select, чтобы получить предметы, и проблема исчезнет.

0 голосов
/ 02 июля 2019

Для тех, у кого похожая проблема, проблема возникает из-за разрешения метода расширения.

Когда я вызываю select с параметрами шаблона, например:

List<Item> newItemList = GetItemsFromDatabase(itemQuery).Select<Item, ItemInfo>(x => new ItemInfo(x.Name, x.Id, x.Guid)).ToList();

, код правильно компилируется.

Похоже, что при разрешении вызова метода после исчерпания методов экземпляра он не может разрешить любой метод расширения, если используется только Select(...).Я предполагаю, что Select явно присутствует, но он не разрешается из-за неоднозначности в определении типа, как IEnumerable<GeneralObject> или IEnumerable<Item>, так и из-за неспособности механизма вывода разрешать типы для вызова Select.И поэтому ни один метод расширения не сопоставляется с ItemCollection, а последняя ошибка говорит о том, что select не определен, вместо того, чтобы указывать, что типы не могут быть выведены для вызова метода расширения и должны быть указаны.

...