在运行时使用字符串定义的名称调用动态对象的成员。

9

我想在利用DLR绑定机制时访问对象上的一个属性。

  • 我无法使用本地绑定机制(C#中的dynamic关键字),因为我不知道编译时的属性名称;
  • 我不能使用反射,因为它只能检索静态类型信息;
  • 据我所知,将其转换为IDictionary<string, object>,只解决了选择实现该接口的动态类的情况(例如ExpandoObject)。

这是演示代码:

    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();
    }
5个回答

6

我最终使用C#运行时绑定器实现了它。(我相信可以使用更简单的绑定器实现,但我无法编写一个可行的 - 可能编写一个“简单”的实现已经非常困难了。)

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();
    }

输出:

StatValue
StatValue
ExpandoValue
ExpandoValue

3
开源框架dynamitey可以实现这一功能(通过nuget可获得)。它使用与C#编译器相同的API调用。
Console.Out.WriteLine(Dynamic.InvokeGet(obj,"Prop"));

在 .NET Core/.NET Standard 中是否有 InvokeGet 方法可用? - Ramki
1
是的,我曾经将这些方法移动到一个新的库中,以便在当时创建一个可移植的库。现在它已经成为了 .net 标准。 - jbtule

0

您可以使用C# dynamic和运行时编译来满足未知属性名称的要求。

例如:

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));

这个方案可以运行,但我怀疑它是否是最佳选项,如果需要调用方法,则可能会变得更加复杂。


0

如果你子类化DynamicObject,你可以重写TrySetMember来将属性按名称存储在本地字典中,并重写TryGetIndex以使用属性名称作为字符串检索它们。在MSDN的DynamicObject页面上有一个非常好的例子。

这不是最快的方法,因为它没有充分利用动态调度和调用站点(以及其他我不太理解的东西),但它可以工作。它最终会看起来像这样:

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"];

1
事实是,我在工作时假设我无法控制我正在使用的动态对象 - 它可以是任何实现IDynamicMetaObjectProvider接口的对象。 - Jean Hominal

0

这基本上是这个问题的变种:动态添加成员到动态对象,但是你想获取成员而不是添加成员 - 因此只需更改绑定器和调用站点的签名。看起来你已经知道如何制作绑定器了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接