2012-05-29 14 views
9

¿Hay alguna manera de requerir varias claves para la función .ToLookup proporcionada por LINQ?ToLookup con varias teclas

Debo admitir que esto parece no ser intuitivo al principio, y estoy esperando que no haya una forma real de hacerlo, pero espero que alguien sepa la manera.

Básicamente, quiero poder buscar por dos valores, por ejemplo, un string y un int, y recuperar el objeto con esos dos valores.

Ejemplo

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

Respuesta

17

me gustaría utilizar Tuple s para este tipo de cosas:

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

no veo cómo esto ayuda. – DarthVader

+0

Gracias Gabe. Voy a intentarlo un poco y dejarte saber cómo funciona. – Merwer

4

Aunque no es lo que realmente quiere, pero lo hará el trabajo muy bien:

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

Así que las otras respuestas están en la línea que yo estaba pensando, pero es algo engorroso creando una tupla o una clase anónima cada vez que desee obtener un valor de la búsqueda. ¿No sería genial si pudiera poner una cadena y un int en un indexador para obtener el valor? Al crear su propia clase para ajustar la búsqueda con un indexador, puede hacer exactamente eso.

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 
} 

Aquí es un ejemplo de que se utilice:

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

Esa es una muy buena idea, pero marqué la respuesta de Gabe como correcta ya que ya había hecho suficiente abstracción para ocultar el código detrás del desarrollo cotidiano. – Merwer

+0

Esto es algo engorroso, pero esto es lo que implementaría si tuviera que hacer este tipo de cosas con frecuencia. – Gabe

+0

@Gabe La clase 'MyLookup' ya está implementada, por lo que cada instancia de la misma no requiere más trabajo para crear que su solución, y realmente el uso de la" búsqueda "resultante es significativamente más fácil. Si valió la pena el tiempo para escribir "MyLookup" es una buena pregunta, pero dado que ya está hecho, la pregunta de si usarla es mucho más fácil de responder. – Servy