18

Is there a way to require multiple keys for the .ToLookup function provided by LINQ?

I will admit that this seems non-intuitive at first, and I'm expecting that there's no actual way to do this, but I'm hoping that someone knows a way.

I basically want to be able to lookup by two values, for example a string and an int, and retrieve the object with those two values.

Example

    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
      }
    }
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Merwer
  • 536
  • 1
  • 7
  • 18

4 Answers4

24

I would use Tuples for this sort of thing:

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)];
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • Thanks Gabe. I'm going to try that in a little bit and let you know how it works out. – Merwer May 29 '12 at 17:29
  • 2
    `Tuple.Create(x.StringProp, x.IntProp)` can be substituted with the shorter `(x.StringProp, x.IntProp)` syntax these days. – thomas Jul 22 '21 at 11:39
7

Although not what you really want, but will do the job just fine:

var r = set.ToLookup(x => new { x.StringProp, x.IntProp });
var f = r[ new { StringProp = "a", IntProp = 1 } ].First();
leppie
  • 115,091
  • 17
  • 196
  • 297
7

So the other answers are along the lines that I was thinking, but it's somewhat cumbersome to be creating a tuple or anonymous class each time you want to just get a value out of the lookup. Wouldn't it be great if you could just stick a string and an int into an indexer to get the value out. By creating your own class to wrap the lookup with an indexer you can do exactly that.

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
}

Here is an example of it being used:

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];
Servy
  • 202,030
  • 26
  • 332
  • 449
  • That's a really good idea, but I marked Gabe's answer as correct as I had already done enough abstraction to hide the code behind day-to-day development. – Merwer May 29 '12 at 17:28
  • This is somewhat cumbersome, but this is what I would implement if I had to do this sort of thing frequently. – Gabe May 29 '12 at 18:00
  • @Gabe The `MyLookup` class is already implemented, so each instance of it requires no more work to create than your solution, and actually using the resulting "lookup" is significantly easier. Whether it was worth the time to write "MyLookup" is a good question, but given that it's already done, the question of whether to use it is much easier to answer. – Servy May 29 '12 at 18:03
  • 3
    Well, if I'm going to put in a class to abstract away multiple keys in a lookup, I'll implement it all the way: `ILookup`, `ToLookup`, provide versions for more than 2 keys, document it, etc. That's quite a bit more work. – Gabe May 29 '12 at 19:03
0

Reviving a pretty old thread this is even easier now since C# 7 with the modern implicitly typed tuples:

//Create a Lookup elegantly with multiple keys via Auto Tuple...
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 
CajunCoding
  • 720
  • 5
  • 9