9

Assuming I cannot use an ExpandoObject and have to roll my own like so :-

class MyObject : DynamicObject {
    dictionary<string, object> _properties = dictionary<string, object>();

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        string name = binder.Name.ToLower();

        return _properties.TryGetValue(name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value) {
        _properties[binder.Name.ToLower()] = value;

        return true;
    }
}

and further down the class hierarchy I have

class MyNewObject : MyObject {
    public string Name {
        get {
            // do some funky stuff
        }
        set {
            // ditto
        }
    }
}

which is quite nice as now I can do the follow :-

dynamic o = MyNewObject();

o.Age = 87;     // dynamic property, handled by TrySetMember in MyObject
o.Name = "Sam"; // non dynamic property, handled by the setter defined in MyNewObject

But the above assumes I know the properties (e.g. Age, Name) at compile time.

Suppose I don't know what they will be until run time.

How can I change the above to support properties I will only know at run time?

Basically I think I am asking is how I can call the code that calls TrySetMember directly so that it will either create a new property or use a getter/setter if one has been defined.

Final Solution as follows :-

using System.Dynamic;
using Microsoft.CSharp.RuntimeBinder;
using System.Runtime.CompilerServices;

class MyObject : DynamicObject {
    Dictionary<string, object> _properties = new Dictionary<string, object>();

    public object GetMember(string propName) {
        var binder = Binder.GetMember(CSharpBinderFlags.None,
              propName, this.GetType(),
              new List<CSharpArgumentInfo>{
                       CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)});
        var callsite = CallSite<Func<CallSite, object, object>>.Create(binder);

        return callsite.Target(callsite, this);
    }

    public void SetMember(string propName, object val) {
        var binder = Binder.SetMember(CSharpBinderFlags.None,
               propName, this.GetType(),
               new List<CSharpArgumentInfo>{
                       CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                       CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)});
        var callsite = CallSite<Func<CallSite, object, object, object>>.Create(binder);

        callsite.Target(callsite, this, val);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        string name = binder.Name.ToLower();

        return _properties.TryGetValue(name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value) {
        _properties[binder.Name.ToLower()] = value;

        return true;
    }
}
mfc
  • 3,018
  • 5
  • 31
  • 43
  • Why would you not implement `IDictionary` as well? [See](http://stackoverflow.com/questions/2974008/adding-unknown-at-design-time-properties-to-an-expandoobject) – nawfal Jul 19 '14 at 20:43
  • Thanks a ton for providing a non-library based solution! – Ken Jun 20 '19 at 20:18

2 Answers2

11

Although the c# compiler is translating dynamic keyword usage to invocation with the dlr using a string name, those Apis are difficult to use directly without the compilers help. The open source framework Dynamitey (available through nuget as a PCL library) encapsulates the dlr API, to make it easy so that you can just call Impromptu.InvokeSet(target, name, value).

using Dynamitey;
...

dynamic o = MyNewObject();

Dynamic.InvokeSet(o,"Age" ,87); 
Dynamic.InvokeSet(o,"Names" ,"Sam");   

Getters and Setters are the least complicated to use the actual Microsoft API directly, so if you don't want to use the 3rd party framework going to the source is an option too.

using Microsoft.CSharp.RuntimeBinder;
using System.Runtime.CompilerServices;
...

  dynamic o = MyNewObject();
  var binder = Binder.SetMember(CSharpBinderFlags.None,
                   "Age",
                   typeof(object),
                   new List<CSharpArgumentInfo>{
                           CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                           CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                                               });

  var callsite = CallSite<Func<CallSite, object, object, object>>.Create(binder);

  callsite.Target(callsite,o,87);

  var binder2 =Binder.SetMember(CSharpBinderFlags.None,
                   "Name",
                   typeof(object),
                   new List<CSharpArgumentInfo>{
                           CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                           CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                                               });
  var callsite2 = CallSite<Func<CallSite, object, object, object>>.Create(binder2);

  callsite2.Target(callsite2,o,"Sam");
  

No StackOverflow, there is nothing to improve

Community
  • 1
  • 1
jbtule
  • 31,383
  • 12
  • 95
  • 128
  • 1
    cool. using binders works great. unfortunately working at a place that doesn't allow open source or third party libraries. – mfc Aug 23 '12 at 14:11
6

But the above assumes I know the properties (e.g. Age, Name) at compile time.

Suppose I don't know what they will be until run time.

Then the dynamic typing in C# 4 doesn't really help you at all, and you might as well just use Dictionary<string, object>.

Rather than assume that dynamic is the answer, I suggest you take a close look at your requirements, and work out what you're really trying to achieve. Once you've got a well-specified set of requirements, it's going to be easier to implement them.

You may find that you just need to make MyObject also implement IDictionary<string, object> like ExpandoObject does... although the problem there is that if you want to derive other classes from MyObject and have their properties exposed via the dictionary too, that's going to be trickier.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Hi, thanks for answer. I don't really have any real requirements, just playing with the functionality. However I did want a real class in this instance because I wanted the ability to define setters or getters for some properties I know about in advance yet still be able to later dynamically add other unknown properties later. – mfc Jul 20 '14 at 04:40