Why can't I do this: dynamic x = new ExpandoObject { Foo = 12, Bar = "twelve" }
Asked Answered
K

7

79

Am I doing something wrong, or is the following code really not possible?

dynamic x = new ExpandoObject { Foo = 12, Bar = "twelve" };

If this really isn't possible, is there another one-line way to instantiate an ExpandoObject with two properties?

Why would the C# team opt to disallow the same initialization syntax as for regular objects, anonymous objects, and enumerables/lists?

Update

I asked this question because I was trying show a Pearl enthusiast the cool new dynamic features of C#, but then I was stalled by not being able to do what I thought was a logical instantiation of an ExpandoObject. Thanks to Hans Passant's answer, I realize that ExpandoObject was the wrong tool for the job. My real goal was to use the dynamic features of C# to return two named values from a method. As Hans points out, the dynamic keyword is perfect for this. I didn't need an ExpandoObject, with all its overhead, to do this.

So, if you want to return a pair of named values from a method, and you are not concerned about type safety, Intellisense, refactoring, or performance, this works quite nicely:

public dynamic CreateFooBar()
{
    return new { Foo = 42, Bar = "Hello" };
}

Usage:

dynamic fooBar = CreateFooBar();
var foo = fooBar.Foo;
var bar = fooBar.Bar;
Kerrin answered 19/9, 2011 at 22:28 Comment(2)
Watch out! The example in your update would only work within the same assembly, since the generated anonymous type is internal.Kiangsu
possible duplicate of C# dynamic object initializer won't compileKhamsin
B
55

Am I doing something wrong, or is the following code really not possible?

It's really not possible. The thing on the left of the assignment operator has to be a property or field known at compile time, and obviously that is not the case for expando objects.

Why would the C# team opt to disallow the same initialization syntax as for regular objects, anonymous objects, and enumerables/lists?

The way you phrase the question indicates the logical error. Features are not implemented by default and then we run around disallowing almost all of them because we think they're a bad idea! Features are unimplemented by default, and have to be implemented in order to work.

The first step in implementing any feature is that someone has to think of it in the first place. To my knowledge, we never did. In particular, it would have been quite difficult for the person designing object initializers in 2006 to know that in 2010 we were going to add "dynamic" to the language, and design the feature accordingly. Features are always designed by designers who move forwards in time, not backwards in time. We only remember the past, not the future.

Anyway, it's a nice idea so thanks for sharing it. Now that someone has thought of it, we can then work on the next steps, like deciding if it is the best idea upon which we can spend our limited budget, designing it, writing the specification, implementing it, testing it, documenting it and shipping it to customers.

I wouldn't expect any of that to happen any time soon; we're a bit busy with this whole async-and-WinRT business that we announced at Build last week.

Birkner answered 19/9, 2011 at 22:34 Comment(7)
n00bs, can't remember the future. :-) jk, +1Commence
Sorry, Eric, I didn't mean to sound attacking. I was just trying to show off to a coworker, who is a python enthusiast, the cool new dynamic features of C#, but then I was stalled by not being able to do what I thought was a logical instantiation of an ExpandoObject. In any case, thanks for your answer, and I hope you'll consider this feature when things settle down with async and WinRT.Kerrin
I'm going to mark this as the answer because it's technically better for the exact question I asked, but I want to mention that Hans' answer better addressed my underlying question, which was, How do you use the dynamic features of C# to return two named values at a time from a method? As Hans points out, you can just use dyanmic--ExpandoObject seems to be intended for more complex scenarios (such as constructing a class at runtime based on the contents of an XML file). So, now that I understand this, I'm no longer as surprised that ExpandoObject doesn't support object initializers.Kerrin
"The thing on the left of the assignment operator has to be a property or field known at compile time, and obviously that is not the case for expando objects." - I'm not quite sure what you mean there...Kiangsu
Please vote for this feature in Visual Studio UserVoice.Pent
It's almost like premature optimization of compiler, failing to psychically take future features into consideration. The syntax implies that it's just assigning members after constructing objects, when in reality it's performing compile-time checks. This is now a compiler issue (or whatever piece you call it) where it's not taking the existence of the DLR into account in this particular situation. There's no reason why the compiler can't figure out that it needs to do something different when the object type is an IDynamicMetaObjectProvider. This should be fixed and supported, IMO.Davao
@Kerrin in your question, your coworker is a Pearl enthusiast :)Khamsin
S
44

There's a better mouse trap than ExpandoObject. The dynamic keyword handles anonymous types with aplomb:

class Program {      
    static void Main(string[] args) {
        dynamic x = new { Foo = 12, Bar = "twelve" };
        Display(x);
    }
    static void Display(dynamic x) {
        Console.WriteLine(x.Foo);
        Console.WriteLine(x.Bar);
    }
}

One unfortunate problem is that the C# compiler generates the anonymous type giving the members only internal accessibility. Which means that you'll get a runtime error when you try to access the members in another assembly. Bummer.

Consider a tuple, much improved in C# v7.

Schrock answered 20/9, 2011 at 0:50 Comment(3)
+1. This is great, Hans. Thanks. I didn't realize you could use dynamic as a return type, but it appears that you can. So, I didn't even need ExpandoObject for my particular scenario.Kerrin
Just know you have to be careful passing anonymous types out side of your method, they can't cross assembly boundaries.Doubleton
Note that you can't change anonymous type member values once set but with ExpandoObject you can. That's a big difference between the 2.Moscow
D
10

Dynamitey (open source PCL and found in nuget) has a syntax for initializing expandos that can be inline.

 //using Dynamitey.DynamicObjects
 var x = Build<ExpandoObject>.NewObject(Foo:12, Bar:"twelve");
Doubleton answered 19/9, 2011 at 23:46 Comment(6)
How would this be possible, are Foo and Bar parameter names?Khamsin
Yes, parameter names are send with the dlr call. So my build object proxy, converts parameter names to property setter calls.Doubleton
An ExpandoObject is a dynamic prototype object, it's just like an object in Javascript, you add properties dynamically, they are never predefined.Doubleton
This was the winner for me. I'm sure annoyed I have to jump through these hoops when all i wanted to do was View(new { Items = model } )Fractocumulus
Agree this answer is cool. Would have liked a bit of syntax sugar, may be something like var x = New.Expando(Foo:12, Bar:"twelve");Khamsin
You can also do dynamic New = Builder.New<ExpandoObject>(); and then use the syntax you expressed.Doubleton
G
6

of course you can!!! with the extended method below you can make something like this:

dynamic x = (new { Foo = 12, Bar = "twelve" }).ToExpando();

here goes the code

public static class MyExpando
{

    public static void Set(this ExpandoObject obj, string propertyName, object value)
    {
        IDictionary<string, object> dic = obj;
        dic[propertyName] = value;
    }

    public static ExpandoObject ToExpando(this object initialObj)
    {
        ExpandoObject obj = new ExpandoObject();
        IDictionary<string, object> dic = obj;
        Type tipo = initialObj.GetType();
        foreach(var prop in tipo.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
        {
            dic.Add(prop.Name, prop.GetValue(initialObj));
        }
        return obj;
    }
}

the Set method is intended for adding more properties (for scenarios where you don't know the property name at compile time)

x.Set("Name", "Bill");

and since it's dynamic could just make

x.LastName = "Gates";
Glassful answered 9/1, 2020 at 0:39 Comment(0)
P
3

One workaround that worked for me is this ToExpando() extension method by Yan Cui (source):

public static class DictionaryExtensionMethods
{
    /// <summary>
    /// Extension method that turns a dictionary of string and object to an ExpandoObject
    /// </summary>
    public static ExpandoObject ToExpando(this IDictionary<string, object> dictionary)
    {
        var expando = new ExpandoObject();
        var expandoDic = (IDictionary<string, object>) expando;

        // go through the items in the dictionary and copy over the key value pairs)
        foreach (var kvp in dictionary)
        {
            // if the value can also be turned into an ExpandoObject, then do it!
            if (kvp.Value is IDictionary<string, object>)
            {
                var expandoValue = ((IDictionary<string, object>) kvp.Value).ToExpando();
                expandoDic.Add(kvp.Key, expandoValue);
            }
            else if (kvp.Value is ICollection)
            {
                // iterate through the collection and convert any strin-object dictionaries
                // along the way into expando objects
                var itemList = new List<object>();
                foreach (var item in (ICollection) kvp.Value)
                {
                    if (item is IDictionary<string, object>)
                    {
                        var expandoItem = ((IDictionary<string, object>) item).ToExpando();
                        itemList.Add(expandoItem);
                    }
                    else
                    {
                        itemList.Add(item);
                    }
                }

                expandoDic.Add(kvp.Key, itemList);
            }
            else
            {
                expandoDic.Add(kvp);
            }
        }

        return expando;
    }
}

Example usage:

public const string XEntry = "ifXEntry";
public static readonly dynamic XEntryItems = new Dictionary<string, object>
{
    { "Name",                     XEntry + ".1" },
    { "InMulticastPkts",          XEntry + ".2" },
    { "InBroadcastPkts",          XEntry + ".3" },
    { "OutMulticastPkts",         XEntry + ".4" },
    { "OutBroadcastPkts",         XEntry + ".5" },
    { "HCInOctets",               XEntry + ".6" },
    { "HCInUcastPkts",            XEntry + ".7" },
    { "HCInMulticastPkts",        XEntry + ".8" },
    { "HCInBroadcastPkts",        XEntry + ".9" },
    { "HCOutOctets",              XEntry + ".10" },
    { "HCOutUcastPkts",           XEntry + ".11" },
    { "HCOutMulticastPkts",       XEntry + ".12" },
    { "HCOutBroadcastPkts",       XEntry + ".13" },
    { "LinkUpDownTrapEnable",     XEntry + ".14" },
    { "HighSpeed",                XEntry + ".15" },
    { "PromiscuousMode",          XEntry + ".16" },
    { "ConnectorPresent",         XEntry + ".17" },
    { "Alias",                    XEntry + ".18" },
    { "CounterDiscontinuityTime", XEntry + ".19" },
}.ToExpando();

Then can use properties like XEntryItems.Name.

PS: Please vote here to support object initializers on ExpandoObjects.

Pent answered 3/3, 2017 at 19:25 Comment(0)
P
0

That kind of initializer syntax is possible because there are already property with a get and setter. With the expando object there aren't those properties yet from what I can tell.

Proa answered 19/9, 2011 at 22:34 Comment(0)
A
0
public static class ExpandoObjectExtentions
{
    public static void Add(this ExpandoObject obj, string key, object val)
    {
        ((IDictionary<string, object>)obj).Add(key, val);
    }
}
Anodyne answered 22/12, 2022 at 13:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.