202

I have used the "select" keyword and extension method to return an IEnumerable<T> with LINQ, but I have a need to return a generic Dictionary<T1, T2> and can't figure it out. The example I learned this from used something in a form similar to the following:

IEnumerable<T> coll = from x in y 
    select new SomeClass{ prop1 = value1, prop2 = value2 };

I've also done the same thing with extension methods. I assumed that since the items in a Dictionary<T1, T2> can be iterated as KeyValuePair<T1, T2> that I could just replace "SomeClass" in the above example with "new KeyValuePair<T1, T2> { ...", but that didn't work (Key and Value were marked as readonly, so I could not compile this code).

Is this possible, or do I need to do this in multiple steps?

Thanks.

Rich
  • 36,270
  • 31
  • 115
  • 154

4 Answers4

349

The extensions methods also provide a ToDictionary extension. It is fairly simple to use, the general usage is passing a lambda selector for the key and getting the object as the value, but you can pass a lambda selector for both key and value.

class SomeObject
{
    public int ID { get; set; }
    public string Name { get; set; }
}

SomeObject[] objects = new SomeObject[]
{
    new SomeObject { ID = 1, Name = "Hello" },
    new SomeObject { ID = 2, Name = "World" }
};

Dictionary<int, string> objectDictionary = objects.ToDictionary(o => o.ID, o => o.Name);

Then objectDictionary[1] Would contain the value "Hello"

Quintin Robinson
  • 81,193
  • 14
  • 123
  • 132
69

A more explicit option is to project collection to an IEnumerable of KeyValuePair and then convert it to a Dictionary.

Dictionary<int, string> dictionary = objects
    .Select(x=> new KeyValuePair<int, string>(x.Id, x.Name))
    .ToDictionary(x=>x.Key, x=>x.Value);
Antoine Meltzheim
  • 9,579
  • 6
  • 35
  • 41
  • 2
    Is it possible to remove .ToDictionary(x=>x.Key, x=>x.Value); and replace new KeyValuePair with new Dictionary ? – Amir Hajiha Jun 19 '18 at 14:04
  • @AmirHajiha - no, because the `new KeyValuePair...` part is called _for each item_. It needs to be code that returns whatever the items are (e.g. KeyValuePair), not the whole collection (e.g. Dictionary). Once you've created all the items you have an `IEnumerable` and you can convert that into a dictionary. – Rory Dec 09 '22 at 09:10
  • 1
    @Antoine - there's no need to use `KeyValuePair` here, rather an anonymous type works just as well and somewhat more concise, e.g. `objects.Select(x=> new { x.Id, x.Name }).ToDictionary(x=>x.Id, x=>x.Name)` – Rory Dec 09 '22 at 09:24
46
var dictionary = (from x in y 
                  select new SomeClass
                  {
                      prop1 = value1,
                      prop2 = value2
                  }
                  ).ToDictionary(item => item.prop1);

That's assuming that SomeClass.prop1 is the desired Key for the dictionary.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
albertein
  • 26,396
  • 5
  • 54
  • 57
0

I modified the solution of hross because his version puts alle items of an array into one dictionary element.

I wanted to have each array item in its own dictionary item. This has two advantages:

  • It is easier to work with if you have arrays which contain objects or other array instead of values.
  • The dictionary key is compatible with the JSON path of Json.Net.

The key to this solution is the fact that JToken knows its path (JToken.Path) so it is not necessary to assemble it for yourself. This makes the new solution surprisingly simple.

public static Dictionary<string, string?>? GetJsonDictionary(JToken? token)
{
    if (token == null)
    {
        return null;
    }
    var dict = new Dictionary<string, string?>();
    ParseJToken(token, dict);
    return dict;
}

private static void ParseJToken(JToken token, Dictionary<string, string?> nodes)
{
    if (token.HasValues)
    {
        // The node has children.
        foreach (JToken child in token.Children())
        {
            ParseJToken(child, nodes);
        }
    }
    else
    {
        // The node is a leaf.
        nodes.Add(token.Path, token.ToString());
    }
}
M.Wolff
  • 131
  • 1
  • 2
  • 7