2012-05-29 3 views
9

Czy istnieje sposób na żądanie wielu kluczy dla funkcji .ToLookup dostarczanej przez LINQ?ToLookup z wieloma kluczami

Przyznam, że na pierwszy rzut oka wydaje się to nieintuicyjne, i spodziewam się, że nie ma na to żadnego sposobu, ale mam nadzieję, że ktoś zna sposób.

Zasadniczo chcę móc wyszukiwać według dwóch wartości, na przykład string i int, i pobrać obiekt z tymi dwiema wartościami.

Przykład

public class MyClass { 
     public string StringProp {get;set;} 
     public int IntProp {get;set;} 
     public object MoreData {get;set;} 
    } 

    public class Main { 
     public void Main() { 
     HashSet<MyClass> set = new HashSet<MyClass>(); 
     set.Add(new MyClass {StringProp = "a", IntProp = 1, MoreData = null}); 
     set.Add(new MyClass {StringProp = "c", IntProp = 4, MoreData = new object()}); 
     set.Add(new MyClass {StringProp = "a", IntProp = 2, MoreData = "upupdowndown"}); 
     set.Add(new MyClass {StringProp = "c", IntProp = 1, MoreData = string.Empty}); 
     set.Add(new MyClass {StringProp = "c", IntProp = 4, MoreData = string.Empty}); 
     // Using 'var' because I don't know how this would be defined. 
     // I recognize that this will not compile - but this is what I'm trying to do. 
     var lookup = set.ToLookup(x => x.StringProp && x.IntProp) 
     MyClass c = lookup["a", 1].First(); // Should return the first element 
     IEnumerable<MyClass> list = lookup["c", 4]; // Should return the 2nd and last elements 
     } 
    } 

Odpowiedz

17

użyłbym Tuple s dla tego typu rzeczy:

var lookup = set.ToLookup(x => Tuple.Create(x.StringProp, x.IntProp)); 
MyClass c = lookup[Tuple.Create("a", 1)].First(); 
IEnumerable<MyClass> list = lookup[Tuple.Create("c", 4)]; 
+0

dont zobaczyć, jak to pomaga. – DarthVader

+0

Dzięki Gabe. Mam zamiar spróbować tego trochę i dać ci znać, jak to działa. – Merwer

4

Chociaż nie jest to, czego naprawdę chcą, ale będzie wykonać zadanie dobrze:

var r = set.ToLookup(x => new { x.StringProp, x.IntProp }); 
var f = r[ new { StringProp = "a", IntProp = 1 } ].First(); 
7

Więc inne odpowiedzi są zgodne z liniami, które myślałem, ale to trochę kłopotliwe, aby być tworzenie krotki lub anonimowej klasy za każdym razem, gdy chcesz uzyskać wartość z wyszukiwania. Czy nie byłoby wspaniale, gdybyś mógł po prostu wstawić string i int do indeksu, aby uzyskać wartość. Tworząc własną klasę, aby zawinąć odnośnik za pomocą indeksera, możesz zrobić dokładnie to.

public class MyLookup<T1, T2, TOut> 
{ 
    private ILookup<Tuple<T1, T2>, TOut> lookup; 
    public MyLookup(IEnumerable<TOut> source, Func<TOut, Tuple<T1, T2>> keySelector) 
    { 
     lookup = source.ToLookup(keySelector); 
    } 

    public IEnumerable<TOut> this[T1 first, T2 second] 
    { 
     get 
     { 
      return lookup[Tuple.Create(first, second)]; 
     } 
    } 

    //feel free to either expose the lookup directly, or add other methods to access the lookup 
} 

Oto przykład jego wykorzystania:

IEnumerable<MyClass> data = null; //TODO populate with real data 
var lookup = new MyLookup<string, int, MyClass>(data 
    , item => Tuple.Create(item.StringProp, item.IntProp)); 

IEnumerable<MyClass> someValue = lookup["c", 4]; 
+0

To naprawdę dobry pomysł, ale zaznaczyłem odpowiedź Gabe'a jako poprawną, ponieważ zrobiłem wystarczająco dużo abstrakcji, aby ukryć kod związany z codziennym rozwojem. – Merwer

+0

To jest trochę kłopotliwe, ale to jest to, co mógłbym zastosować, gdybym musiał robić to często. – Gabe

+0

@Gabe Klasa 'MyLookup' jest już zaimplementowana, więc każda jej instancja nie wymaga więcej pracy niż rozwiązanie, a korzystanie z wynikowego" wyszukiwania "jest znacznie łatwiejsze. Czy warto było napisać "MyLookup" to dobre pytanie, ale biorąc pod uwagę, że już zostało to zrobione, pytanie, czy go używać, jest o wiele łatwiejsze. – Servy