Elegant way to go from list of objects to dictionary with two of the properties

52,357

Solution 1

With LINQ:

var fooDict = foos.ToDictionary(x=>x.Name,x=>x.StreetAddress);

(and yes, fooDict is Dictionary<string, string>)


edit to show the pain in VS2005:

Dictionary<string, string> fooDict =
    Program.ToDictionary<Foo, string, string>(foos,
        delegate(Foo foo) { return foo.Name; },
        delegate(Foo foo) { return foo.StreetAddress; });

where you have (in Program):

public static Dictionary<TKey, TValue> ToDictionary<TSource, TKey, TValue>(
    IEnumerable<TSource> items,
    Converter<TSource, TKey> keySelector,
    Converter<TSource, TValue> valueSelector)
{
    Dictionary<TKey, TValue> result = new Dictionary<TKey, TValue>();
    foreach (TSource item in items)
    {
        result.Add(keySelector(item), valueSelector(item));
    }
    return result;
}

Solution 2

If you are using framework 3.5, you can use the ToDictionary extension:

Dictionary<string, string> fooDict = foos.ToDictionary(f => f.Name, f => f.StreetAddress);

For framework 2.0, the code is pretty much as simple as it can be.

You can improve the performance a bit by specifying the capacity for the dictionary when you create it, so that it doesn't have to do any reallocations while you fill it:

Dictionary<string, string> fooDict = new Dictionary<string, string>(foos.Count):

Solution 3

Without LINQ, no, there's no built-in helpers for this. You could write one though:

// I forget if you need this delegate definition -- this may be already defined in .NET 2.0
public delegate R Func<T,R>(T obj);
public static Dictionary<K,V> BuildDictionary<T,K,V>(IEnumerable<T> objs, Func<T,K> kf, Func<T,V> vf)
{
    Dictionary<K,V> d = new Dictionary<K,V>();
    foreach (T obj in objs)
    {
        d[kf(obj)] = vf(obj);
    }
    return d;
}

Dictionary<string, string> fooDict = BuildDictionary(foos, new Func<Foo,string>(delegate(Foo foo) { return foo.Name; }), new Func<Foo,string>(delegate(Foo foo) { return foo.StreetAddress; }));

It doesn't look nearly as elegant as the LINQ-based answers, does it...

Solution 4

Here's a solution that's .net 2.0 compatible that uses System.Web.UI.Databinder to do the reflection on the property name - you lose compile-time type checking.

        public static Dictionary<string, string> ToDictionary<T>(List<T> list, string keyName, string valueName)
    {
        Dictionary<string, string> outputDictionary = new Dictionary<string, string>();
        foreach (T item in list)
        {
            string key = Eval<T, string>(item, keyName);
            string value = Eval<T, string>(item, valueName);
            output[key] = value;
        }

        return outputDictionary;
    }

    public static TOut Eval<TIn, TOut>(TIn source, string propertyName)
    {
        object o = DataBinder.GetPropertyValue(source, propertyName);
        if (o is TOut)
            return (TOut)o;

        return default(TOut);
    }

You would call as follows:

Dictionary<string, string> fooDict = ToDictionary(foos, "Name", "StreetAddress");
Share:
52,357
leora
Author by

leora

Updated on June 02, 2020

Comments

  • leora
    leora almost 4 years

    i seem to write this code over and over again and wanted to see if there was a better way of doing it more generically.

    I start out with a list of Foo objects

    Foo[] foos = GenerateFoos();
    

    I think want to create a dictionary where the key and value are both properties of Foo

    for example:

    Dictionary<string, string> fooDict = new Dictionary<string, string>():
    foreach (Foo foo in foos)
    {
        fooDict[foo.Name] = foo.StreetAddress;
    }
    

    is there anyway of writing this code generically as it seems like a basic template where there is an array of objects, a key property a value property and a dictionary.

    Any suggestions?

    I am using VS 2005 (C#, 2.0)

  • Marc Gravell
    Marc Gravell almost 15 years
    Well, you could write something similar using anonymous methods and a utility class, but it would probably be more work than your existing code...
  • Paul Suart
    Paul Suart almost 15 years
    Oh, that's nice. Much better than mine :(
  • Marc Gravell
    Marc Gravell almost 15 years
    I'd need to check, but you might find you need to specify the generic types... the generic type inference got stronger in C# 3.0 (it wasn't all that great in C# 2.0/VS2005)
  • Coryza
    Coryza almost 15 years
    I'm pretty sure generic type inference was in 2.0, but delegate inference wasn't -- if it was, you could drop the "new Func<Foo,string>" stuff.
  • Coryza
    Coryza almost 15 years
    Of course, I always seem to forget what of this stuff is in 2.0 - I just let the compiler's complaints be my memory device :)
  • Marc Gravell
    Marc Gravell almost 15 years
    Ah yes; we're both right! With the new Func<Foo,string>, the generic type inference is happy; but without (i.e. just delegate {...}) you need to specify the generic types ;-p
  • Camilo Martin
    Camilo Martin almost 12 years
    Man, that syntax is beautiful. I can't imagine how the compiler does all of that - I'm aware it's doing a lot. I mean, the intellisense too!
  • idbrii
    idbrii almost 5 years
    Since you know the number of items, you might as well set the initial capacity of the dictionary: new Dictionary<TKey, TValue>(items.Count())