2012-05-29 17 views
9

C'è un modo per richiedere più chiavi per la funzione .ToLookup fornita da LINQ?ToLookup con più chiavi

Ammetto che all'inizio non sembra intuitivo, e mi aspetto che non ci sia un modo per farlo, ma spero che qualcuno conosca un modo.

Fondamentalmente voglio essere in grado di cercare due valori, ad esempio uno string e uno int, e recuperare l'oggetto con quei due valori.

Esempio

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

risposta

17

userei Tuple s per questo genere di cose:

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

io non vedere come questo aiuta. – DarthVader

+0

Grazie Gabe. Ci provo un po 'e ti faccio sapere come funziona. – Merwer

4

Anche se non ciò che si vuole veramente, ma farà il lavoro bene:

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

Quindi le altre risposte sono sulla stessa linea che stavo pensando, ma è piuttosto complicato essere creare una classe tupla o anonima ogni volta che si desidera ottenere un valore dalla ricerca. Non sarebbe bello se si potesse semplicemente inserire una stringa e una int in un indicizzatore per ottenere il valore. Creando la tua classe per avvolgere la ricerca con un indicizzatore, puoi fare esattamente questo.

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 
} 

Ecco un esempio di esso in uso:

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

Questa è davvero una buona idea, ma ho segnato la risposta di Gabe come corretta poiché avevo già fatto abbastanza astrazioni per nascondere il codice dietro lo sviluppo giorno per giorno. – Merwer

+0

Questo è un po 'macchinoso, ma questo è quello che implementerei se dovessi fare questo genere di cose frequentemente. – Gabe

+0

@Gabe La classe 'MyLookup' è già implementata, quindi ogni istanza di esso non richiede più lavoro da creare rispetto alla soluzione, e in realtà l'utilizzo della" ricerca "risultante è notevolmente più semplice. Se valesse la pena di scrivere "MyLookup" è una buona domanda, ma dato che è già stato fatto, la domanda se usarlo è molto più facile da rispondere. – Servy