LINQ запутывается при одновременной реализации IEnumerable <T> - программирование
Подтвердить что ты не робот

LINQ запутывается при одновременной реализации IEnumerable <T>

Мой класс реализует IEnumerable<T> дважды. Как я могу заставить LINQ работать без кастования hashtable каждый раз?


Я написал свою собственную ковариантную реализацию хэш-таблицы, которая также наследуется от .NET IDictionary<TKey, TValue>. В конечном итоге он реализует IEnumerable<T> дважды с разными типами для T. Я реализовал первичный перечислимый интерфейс неявно, а другой - явно. Что-то вроде этого (псевдокод):

class HashTable<TKey, TValue> :
    ...
    IEnumerable<out IAssociation<out TKey, out TValue>>,
    IEnumerable<out KeyValuePair<TKey, TValue>>
{
    // Primary:
    public IEnumerator<IAssociation<TKey, TValue>> GetEnumerator();
    // Secondary:
    IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator();
}

Когда я foreach хеш-таблица, она принимает как ожидалось первичный перечислимый:

using System;
using System.Collections.Generic;
using System.Linq;

var hashtable = new HashTable<string, int>();
foreach (var kv in hashtable)
{
    // kv is IAssociation<string, int>
}

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

var xs1 = from x in hashtable          // <-- 1
          select x;

var xs2 = hashtable.Select(x => x);    // <-- 2

Ошибка 1: Не удалось найти реализацию шаблона запроса для типа источника "HashTable" . "Выбрать" не найдено. Рассмотрим явно задание типа переменной диапазона "x".

Ошибка 2: "HashTable" не содержит определения для "Выбрать" , и не может быть найден метод расширения "Выбрать" , принимающий первый аргумент типа "HashTable" (вам не хватает директивы using или ссылки на сборку?)

Может быть, есть какой-то интерфейс или трюк наследования, о которых я не знаю?


Для тех, кто спросил, вот полное дерево интерфейсов:

using SCG = System.Collections.Generic;

public class HashTable<TKey, TValue>
    : IKeyedCollection<TKey, TValue>, SCG.IDictionary<TKey, TValue>

public interface IKeyedCollection<out TKey, out TValue>
    : ICollection<IAssociation<TKey, TValue>>

public interface ICollection<out T> : SCG.IEnumerable<T>

public interface IAssociation<out TKey, out TValue>

// .NET Framework:
public interface IDictionary<TKey, TValue>
    : ICollection<KeyValuePair<TKey, TValue>>

public interface ICollection<T>
    : IEnumerable<T>

Теперь вы можете понять, почему я не мог сделать теги KeyValuePair<TKey, TValue> и IAssociation<TKey, TValue>.

4b9b3361

Ответ 1

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

Простейшим подходом (ИМО) было бы введение двух новых свойств:

public IEnumerable<IAssociation<TKey, TValue>> Associations { get { return this; } }
public IEnumerable<KeyValuePair<TKey, TValue>> KeyValuePairs { get { return this; } }

Это означает, что вы действительно можете быть конкретным:

var query = from x in table.Associations
            ...;

или

var query = from x in table.KeyValuePairs
            ...;

Не только это помогает сохранить компилятор счастливым - это поможет любому, кто пытается прочитать код. Если вы обнаружите, что используете один из них гораздо больше, чем другой, вы всегда можете сделать HashTable только реализовать одиночный IEumerable<> и набрать и сохранить другое свойство.