22

I'm going nuts here. It's probably because I've missed some rule, but if so, then please tell me.

I'm trying to create a Dictionary with a string for key and an anonymous object as value. Or, actually I'm not just trying, I'm doing it. But when I want to alter a specific parameter in the object, it goes wrong.

I declare the dictionary like this:

var areas = new Dictionary<string, object>
{
  {"Key1", new {name = "Name1", today = 0, all = 0}},
  {"Key2", new {name = "Name2", today = 0, all = 0}},
    ...
}

And then I'm assuming I can do this:

foreach (var area in areas.Keys.ToArray())
{
    var areaname = areas[area].name;
}

but Visual Studio does not agree, and refuses to compile. If I write this, though, everything works as I would think - but that doesn't really help me out, it's just making me more frustrated.

var areaname = new 
            {
                 name = "Nametest", today = 0, all = 0
            };
var testname = areaname.name;

What am I doing wrong? Why isn't it working for me? Is it simply impossible to do so?

Thanks for all your answers, they surely cleared things up a bit! I think I might have been confusing the type object with the idea of objects, that is classes, in C# out of frustration. I'll rethink the whole design business and probably do something completely different instead of using evil or dirty solutions. Though interesting, I don't think my relationship with C# has evolved that far yet!

Irshad
  • 3,071
  • 5
  • 30
  • 51
DummeDitte
  • 265
  • 1
  • 2
  • 11
  • 1
    Did you try using the "Dynamic" instead of "Object" for your value type? – Louis Ricci Sep 06 '11 at 14:09
  • 1
    The compiler is not able to resolve the Name property on the object (value) that you retrieve from the dictionary. For the var case, the compiler puts in the real on-the-fly type in place of var, assisting the compiler. Why do you need anonymous types in this case ? When you know what properties should exist on all values i.e. it is not unknown. – Gishu Sep 06 '11 at 14:10

5 Answers5

31

This will not work because you have declared the dictionary as Dictionary<string, object>.

You could instead try Dictionary<string, dynamic>.

Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
10

This will not work because you have declared the dictionary as Dictionary<string, object>.

C# creates a new type in the background for you (which you can't access) that has these properties (herewith known as an anonymous type). The only time you can use it directly is when it is still var type - as soon as you add it to that dictionary it is cast to object and thus you can't access the properties any more.

Option 1

You could instead try Dictionary<string, dynamic>. Dynamic will be able to access these properties - but this is only supported in C#/.Net 4.0

Option 2

You can get the type back using a technique known as demonstration (cast by example). This isn't a documented feature, is wasteful and is somewhat a hack. This is a horrible evil trick - especially because it won't work across assemblies.

public static T Demonstrate<T>(this T demonstration, object value)
{
    return (T)value;
}

You can then access the original properties like so:

var area = new { name = "", today = 0, all = 0 }.Demonstrate(areas[name]);
var areaName = value.name;

Option 3 This is honestly the best way

Write a class. If you like the fact that the C# compiler does all of the Equals, GetHashCode and ToString work for you, you can just use something like ILSpy to grab the original code. Thus:

class AreaInfo
{
    public string Name { get; set; }
    public int Total { get; set; }
    public int All { get; set; }

    public override bool Equals(object obj)
    {
        var ai = obj as AreaInfo;
        if (object.ReferenceEquals(ai, null))
            return false;
        return Name == ai.Name && Total == ai.Total && All == ai.All;
    }

    public override int GetHashCode()
    {
        var hc = 0;
        if (Name != null)
            hc = Name.GetHashCode();
        hc = unchecked((hc * 7) ^ Total);
        hc = unchecked((hc * 21) ^ All);
        return hc;
    }

    public override string ToString()
    {
        return string.Format("{{ Name = {0}, Total = {1}, All = {2} }}", Name, Total, All);
    }
}

Correct your dictionary:

var areas = new Dictionary<string, AreaInfo>       
{       
  {"Key1", new AreaInfo() {Name = "Name1", Today = 0, All = 0}},       
  {"Key2", new AreaInfo() {Name = "Name2", Today = 0, All = 0}},       
    ...       
};

And now things will work the way you expect them to.

Community
  • 1
  • 1
Jonathan Dickinson
  • 9,050
  • 1
  • 37
  • 60
  • 9
    Your "Demonstrate" method (often also called "CastByExample") is not horrible because it might break in a future version of the language; it is perfectly legal code and there is no implementation-defined behaviour there. That should continue to work forever. However, it is horrible because *it will not work across assemblies*. If you have a dictionary of anonymous type created by assembly ABC.dll, and assembly XYZ.dll attempts to cast by example, the cast will fail even if the anonymous types have the same "shape". *Anonymous types are only identical within the assembly that created them.* – Eric Lippert Sep 06 '11 at 15:09
  • @Eric - I know you work for MSFT :), but doesn't it fall into the realm of undocumented behaviour? – Jonathan Dickinson Sep 06 '11 at 15:40
  • 3
    @Jonathan I went and checked - C# 3.0 spec, 7.5.10.6: "Within the same program, two anonymous object initializers that specify a sequence of properties of the same names and compile-time types in the same order will produce instances of the same anonymous type. " – AakashM Sep 06 '11 at 15:47
  • You forgot how to include the code to pull AreaInfo out of the dictionary – Demodave Jun 26 '20 at 14:22
9

This doesn't compile, because the values in your dictionary are of type object and object doesn't contain a property name.

If you are using .NET 4 you could change the type from object to dynamic.

Having said this, I strongly disagree with this design decision. You really should create a class for this.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
8

To get your Dictionary to have the proper anonymous type for its value, do this:

var areas=new[] {
  new {Key="Key1", Value=new {name="Name1", today=0, all=0}},
  new {Key="Key2", Value=new {name="Name2", today=0, all=0}},
}.ToDictionary(kv => kv.Key, kv => kv.Value);

Then your code will work as expected:

foreach(var area in areas.Keys.ToArray()) {
  var areaname=areas[area].name;
}
Corey Kosak
  • 2,615
  • 17
  • 13
  • @nawfal - I agree that this is the closest answer to the OP's question, however I'm not sure if for readability of code it wouldn't be better to create an actual class (even though usually I try to avoid creating a class which is used only as a 'temporary' data container) – BornToCode Sep 30 '14 at 12:46
  • 1
    @BornToCode yup you're right. Going with actual named types is most probably the best thing to do here. I should have said its the *coolest* solution here (given OP is rigid about anonymous type) :) – nawfal Sep 30 '14 at 13:09
1

The details of anonymous types aren't portable beyond the assembly they are created in, and aren't readily† visible beyond the method they are created in. You should use an onymous type instead, or dynamic if you want to adopt a dynamic approach.

† there is an evil trick here which I'm deliberately not mentioning, because it's evil.

AakashM
  • 62,551
  • 17
  • 151
  • 186
  • 2
    Anonymous types are actually documented as being structurally equivalent *throughout the assembly* which defines them, not just in a method. Also, though "onymous type" is cute, "named type" is better. I sometimes use "nominal type", though that has the unfortunate pejorative connotation of "a type in name only" rather than "a type with a specific name". – Eric Lippert Sep 06 '11 at 15:13