Invoke a member of a dynamic object with a name defined at runtime in a String
Asked Answered
N

5

9

I want to access a property on an object while leveraging the DLR binding mechanism.

  • I cannot use the native binding mechanism (dynamic keyword in C#) because I do not know the property name at compile-time;
  • I cannot use reflection because it only retrieves static type information;
  • casting to an IDictionary<string, object>, to my knowledge, only solves the case of dynamic classes that choose to implement that interface (such as ExpandoObject).

Here is the demonstration code:

    static void Main(string[] args)
    {
        dynamic obj = new System.Dynamic.ExpandoObject();
        obj.Prop = "Value";
        // C# dynamic binding.
        Console.Out.WriteLine(obj.Prop);
        // IDictionary<string, object>
        Console.Out.WriteLine((obj as IDictionary<string, object>)["Prop"]);
        // Attempt to use reflection.
        PropertyInfo prop = obj.GetType().GetProperty("Prop");
        Console.Out.WriteLine(prop.GetValue(obj, new object[] { }));
        Console.In.ReadLine();
    }
Neoclassic answered 27/6, 2011 at 10:9 Comment(0)
N
6

I was finally able to do it by using the C# runtime binder. (I believe it should be possible to do that with a simpler binder implementation, but I was unable to write a working one - probably writing a "simple" implementation is already quite tough).

using Microsoft.CSharp.RuntimeBinder;

    private class TestClass
    {
        public String Prop { get; set; }
    }

    private static Func<object, object> BuildDynamicGetter(Type targetType, String propertyName)
    {
        var rootParam = Expression.Parameter(typeof(object));
        var propBinder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, propertyName, targetType, new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
        DynamicExpression propGetExpression = Expression.Dynamic(propBinder, typeof(object), 
            Expression.Convert(rootParam, targetType));
        Expression<Func<object, object>> getPropExpression = Expression.Lambda<Func<object, object>>(propGetExpression, rootParam);
        return getPropExpression.Compile();
    }

    static void Main(string[] args)
    {
        dynamic root = new TestClass { Prop = "StatValue" };

        Console.Out.WriteLine(root.Prop);
        var dynGetter = BuildDynamicGetter(root.GetType(), "Prop");
        Console.Out.WriteLine(dynGetter(root));

        root = new System.Dynamic.ExpandoObject();
        root.Prop = "ExpandoValue";

        Console.WriteLine(BuildDynamicGetter(root.GetType(), "Prop").Invoke(root));
        Console.Out.WriteLine(root.Prop);
        Console.In.ReadLine();
    }

Output:

StatValue
StatValue
ExpandoValue
ExpandoValue
Neoclassic answered 27/6, 2011 at 16:27 Comment(0)
T
3

The opensource framework dynamitey will do this (available via nuget). It uses that same api calls as the c# compiler.

Console.Out.WriteLine(Dynamic.InvokeGet(obj,"Prop"));
Totalitarianism answered 6/7, 2011 at 19:44 Comment(2)
Is InvokeGet method available in .net core/.net standard?Aggravation
Yes, I moved the methods to a new library to make a portable library back in the day. It's since become a .net standardTotalitarianism
R
0

You could use C# dynamic with runtime compilation to accommodate the requirement of the unknown property name.

An example:

dynamic d = new ExpandoObject();

d.Value = 7;

var helper = "" +
    "using System; " +
    "public class Evaluator " + 
    "{{ " + 
    "    public object Eval(dynamic d) {{ return d.{0}; }} " + 
    "}}";

var references = new string[] 
{ 
    "System.dll", 
    "System.Core.dll", 
    "Microsoft.CSharp.dll" 
};

var parameters = new CompilerParameters(references, "Test.dll");
var compiler = new CSharpCodeProvider();

var results = compiler.CompileAssemblyFromSource(
    parameters,
    String.Format(helper, "Value"));

dynamic exp = Activator.CreateInstance(
    results.CompiledAssembly.GetType("Evaluator"));

Console.WriteLine(exp.Eval(d));

This works, but I doubt that this is the best option and if you need invoke method it can get a bit more complex.

Recapture answered 27/6, 2011 at 10:34 Comment(0)
P
0

If you subclass DynamicObject you can override TrySetMember to store the properties by name in a local dictionary, and override TryGetIndex to enable retrieving them using the property name as a string. There's a pretty good example of this at the DynamicObject page on MSDN.

This is not the fastest way to do it as it doesn't take advantage of dynamic dispatch and callsites (and other stuff I don't quite understand) as well as it could, but it will work. It'll end up looking like this:

dynamic myDynamicClass = new MyDynamic();

// Uses your TrySetMember() override to add "Value" to the internal dictionary against the key "Prop":
myDynamicClass.Prop = "Value";

// Uses your TryGetIndex override to return the value of the internal dictionary entry for "Prop":
object value = myDynamicClass["Prop"];
Pym answered 27/6, 2011 at 11:50 Comment(1)
Thing is, I am working under the assumption that I do not control the dynamic object that I am consuming - it can be any object that implements IDynamicMetaObjectProvider.Neoclassic
R
0

This is basically a variant of this question: Dynamically adding members to a dynamic object but instead of wanting to add you're wanting to get the members - so just change the binder and the signature of the call site. It looks like you've already figured out how to make the binder.

Rifleman answered 28/6, 2011 at 2:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.