A dictionary where value is an anonymous type in C#
Asked Answered
K

5

45

Is it possible in C# to create a System.Collections.Generic.Dictionary<TKey, TValue> where TKey is unconditioned class and TValue - an anonymous class with a number of properties, for example - database column name and it's localized name.

Something like this:

new { ID = 1, Name = new { Column = "Dollar", Localized = "Доллар" } }
Ketosis answered 24/10, 2009 at 23:43 Comment(5)
Very similar (with linq): A generic list of anonymous classAnother
.Select(...).AsEnumerable().ToDictionary(k => k.id, v => v as object) worked for me. My variable was Dictionary<long,object>Eyed
@Ravishankar: You don't need AsEnumerable() here, most probably. It doesn't add anything on top of Select().Ketosis
@abatishchev. True. I wanted to say that "as object" made my life easier. Rest of the code can be ignored.Eyed
@Eyed object is not an anonymous type.Vodka
C
48

You can't declare such a dictionary type directly (there are kludges but these are for entertainment and novelty purposes only), but if your data is coming from an IEnumerable or IQueryable source, you can get one using the LINQ ToDictionary() operator and projecting out the required key and (anonymously typed) value from the sequence elements:

var intToAnon = sourceSequence.ToDictionary(
    e => e.Id,
    e => new { e.Column, e.Localized });
Clackmannan answered 24/10, 2009 at 23:50 Comment(0)
H
21

As itowlson said, you can't declare such a beast, but you can indeed create one:

static IDictionary<TKey, TValue> NewDictionary<TKey, TValue>(TKey key, TValue value)
{
    return new Dictionary<TKey, TValue>();
}

static void Main(string[] args)
{
    var dict = NewDictionary(new {ID = 1}, new { Column = "Dollar", Localized = "Доллар" });
}

It's not clear why you'd actually want to use code like this.

Habilitate answered 25/10, 2009 at 0:8 Comment(3)
Why? I can think of a number of reasons. Here's one. Consider for example a dictionary used to memoize an n-ary function, say a function of four int arguments. In the next version of the CLR of course you'd just use a 4-tuple, but in C# 3, you could create a dictionary of anonymous type {int, int, int, int}, done, no need to define your own tuple type.Physicality
Not clear why you'd want this? How about compound keys (e.g. new { State = "NY", Country = "US" })Prospective
Or compound results that are in a reference type (the new inline tuples are value types).Vodka
M
3

You can do a refection

public static class ObjectExtensions
{
    /// <summary>
    /// Turn anonymous object to dictionary
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    public static IDictionary<string, object> ToDictionary(this object data)
    {
        var attr = BindingFlags.Public | BindingFlags.Instance;
        var dict = new Dictionary<string, object>();
        foreach (var property in data.GetType().GetProperties(attr))
        {
            if (property.CanRead)
            {
                dict.Add(property.Name, property.GetValue(data, null));
            }
        }
        return dict;
    }
}
Mindy answered 19/8, 2010 at 7:52 Comment(1)
Good idea. Also you can make it generic: public static IDictionary<string, T> ToDictionary(this object data) { }Ketosis
V
3

I think ASP.NET MVC didn't exit at the time this question was made. It does convert anonymous objects to dictionaries internally.

Just take a look at the HtmlHelper class, for example. The method that translates objects to dictionaries is the AnonymousObjectToHtmlAttributes. It it's specifc to MVC and returns an RouteValueDictionary, however.

If you want something more generic, try this:

public static IDictionary<string,object> AnonymousObjectToDictionary(object obj)
{
    return TypeDescriptor.GetProperties(obj)
        .OfType<PropertyDescriptor>()
        .ToDictionary(
            prop => prop.Name,
            prop => prop.GetValue(obj)
        );
}

One intersting advantages of this implementation is that it returns an empty dictionary for null objects.

And here's one generic version:

public static IDictionary<string,T> AnonymousObjectToDictionary<T>(
    object obj, Func<object,T> valueSelect
)
{
    return TypeDescriptor.GetProperties(obj)
        .OfType<PropertyDescriptor>()
        .ToDictionary<PropertyDescriptor,string,T>(
            prop => prop.Name,
            prop => valueSelect(prop.GetValue(obj))
        );
}
Voccola answered 4/2, 2012 at 19:40 Comment(4)
Thanks for letting that know! The disadvantage of such method I may name is using reflection.Ketosis
@Ketosis I think it's impossible to solve the problem without reflection. I'm not sure, but reading properties with TypeDescriptor might be more eficient than with a regular .GetType().GetProperties(). BTW, I used ILSpy on HtmlHelper and it does almost exactly the same thing.Voccola
object is not an anonymous type. This does the opposite of what we want. -- The end result is a strong typed dictionary that contains no anonymous types.Vodka
the input is object, the output is IDictionary<string,T>, which is not anonymous. T can be whatever you want. Are people seriously downvoting this answer after 10 years, without adding any extra context? Could you please have the decency of saying what you think is wrong with it?Voccola
G
2

If you would like to initialize an empty dictionary you could do something like the this:

var emptyDict = Enumerable
    .Empty<(int, string)>()
    .ToDictionary(
         x => new { Id = x.Item1 }, 
         x => new { Column = x.Item2, Localized = x.Item2});

Basically you just need an empty enumerable with a tuple that has the types you want to use in your final anonymous types and then you can get an empty dictionary that is typed the way you'd like.

If you wanted to you could name the types in the tuple as well:

var emptyDict = Enumerable
            .Empty<(int anInt, string aString)>()
            .ToDictionary(
                x => new { Id = x.anInt },
                x => new { Column = x.aString, Localized = x.aString});
Grand answered 9/8, 2019 at 12:55 Comment(1)
The tuple is unnecessary and creates a false sense of dependency on the tuple. The key trick here is to call the extension method ToDictionary(), but that can be accomplished using ANY empty enumerable, then assigning literal values to the anonymous type for type inference. Like this: var emptyDictx = Enumerable.Empty<object>().ToDictionary(_ => 0, _ => new { Column = string.Empty, Localized = string.Empty }); Notice that even the lamba parameters can be discards.Terriss

© 2022 - 2024 — McMap. All rights reserved.