Pytanie LINQ jest zdezorientowany po dwukrotnym wdrożeniu IEnumerable


Moja klasa implementuje IEnumerable<T> dwa razy. Jak mogę uzyskać LINQ do pracy bez rzutowania hashtable każdego razu?


Napisałem własną implementację hashtable, która również dziedziczy po .NET IDictionary<TKey, TValue>. W końcu to implementuje IEnumerable<T> dwa razy z różnymi typami dla T. Zaimplementowałem główny interfejs przeliczalny pośrednio, a drugi jawnie. Coś takiego (pseudokod):

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();
}

Kiedy ja foreach tablica asocjacyjna, zajmuje zgodnie z oczekiwaniami główny przeliczalny:

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>
}

Teraz chcę, aby to samo w LINQ, ale zawiesza mnie błędy kompilatora, ponieważ nie wie, który interfejs wybrać dla metod rozszerzenia:

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

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

Błąd 1: Nie można znaleźć implementacji wzorca zapytania dla typu źródła "HashTable". Nie znaleziono "Wybierz". Rozważmy wyraźne określenie typu zmiennej zakresu "x".

Błąd 2: "HashTable" nie zawiera definicji "Wybierz" i nie można znaleźć metody rozszerzenia "Wybierz", akceptując pierwszy argument typu "HashTable" (czy brakuje instrukcji użycia lub odniesienia do zespołu?)

Może jest jakiś interfejs lub sztuczka dziedziczenia, o której nie wiem?


Dla tych, którzy pytają, tutaj jest pełne drzewo interfejsów:

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>

Teraz możesz zobaczyć, dlaczego nie mogłem KeyValuePair<TKey, TValue> i IAssociation<TKey, TValue> to samo.


14
2018-02-14 15:36


pochodzenie


Czy próbowałeś określić ogólne argumenty z Select ręcznie? Wygląda na to, że ma problemy z ich wnioskiem. W szczególności musisz określić TSource ręcznie, ale gdy już to zrobisz, musisz zrobić to, co myślę. Niestety, to uniemożliwi ci powrót anonimowych typów bez irytujących obejść. - Adam Houldsworth
Czy możesz dołączyć resztę dziedziczenia / interfejsów dla HashTable<TKey, TValue>? - Ryan Gates
Cóż, powie ci, co masz zrobić, przynajmniej w odniesieniu do pierwszego błędu. Musisz jednoznacznie powiedzieć, jaki jest typ "x": z IAssociation <TKey, TValue> x w hashtable wybierz x; - Stonehead
Dlaczego jeden z nich korzysta IAssociation<TKey, TValue> i jedno użycie KeyValuePair<TKey, TValue>? Spodziewałbym się, że obaj użyją tego samego typu. - Doctor Jones
Możesz również użyć klasy adaptera, która umiałaby przekonwertować IAssociation na KeyValuePair i na odwrót (przez niejawnych operatorów), a następnie uczynić z niej HashTable <TKey, TValue>. Ale wolałbym pójść z rozwiązaniem Jona Skeeta - Pierluc SS


Odpowiedzi:


Ważne jest, aby zrozumieć, że kompilator nie ma koncepcji implementacji interfejsu "podstawowego" i "dodatkowego", jeśli chodzi o użycie wyrażenia jako argumentu dla wywołania metody. Twój typ implementuje oba IEnumerable<IAssociation<...>> i IEnumerable<KeyValuePair<...>> równie dobrze, jeśli chodzi o konwersje do tych typów. Dlatego kompilator potrzebuje więcej informacji.

Najprostszym podejściem (IMO) byłoby wprowadzenie dwóch nowych właściwości:

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

Oznacza to, że możesz naprawdę być konkretnym:

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

lub

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

Nie tylko pomaga to w utrzymaniu kompilatora w dobrym stanie - pomoże każdemu, kto spróbuje odczytać kod. Jeśli znajdziesz, użyj jednego z nich dużo bardziej niż inne, zawsze możesz zrobić HashTable implementuj tylko jeden IEumerable<> i wpisz i zachowaj inną nieruchomość.


25
2018-02-14 15:41



Bardzo lubię tę odpowiedź. - Doctor Jones
Czy nie ma innego sposobu na zaspokojenie twojego kompilatora poprzez zaimplementowanie ich obu bezpośrednio. - antonijn
@Antonijn: Tak, możesz zadowolić kompilator, jawnie określając argumenty typu ... - Jon Skeet