Reputation: 16796
I want to access a property on an object while leveraging the DLR binding mechanism.
dynamic
keyword in C#) because I do not know the property name at compile-time;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();
}
Upvotes: 9
Views: 3115
Reputation: 31809
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"));
Upvotes: 3
Reputation: 16796
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
Upvotes: 6
Reputation: 6486
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.
Upvotes: 0
Reputation: 7135
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"];
Upvotes: 0
Reputation: 57718
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.
Upvotes: 0