Вызвать член динамического объекта с именем, определенным во время выполнения в строке

Я хочу получить доступ к свойству объекта, используя механизм привязки DLR.

  • Я не могу использовать собственный механизм привязки (ключевое слово dynamic в C #), потому что я не знаю имя свойства во время компиляции;
  • Я не могу использовать отражение, потому что оно извлекает только информацию о статическом типе;
  • приведение к 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();
    }

person Jean Hominal    schedule 27.06.2011    source источник


Ответы (5)


Наконец-то я смог сделать это с помощью связывателя времени выполнения 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
person Jean Hominal    schedule 27.06.2011

Фреймворк с открытым исходным кодом Dynamitey сделает это (доступно через nuget). Он использует те же вызовы API, что и компилятор C #.

Console.Out.WriteLine(Dynamic.InvokeGet(obj,"Prop"));
person jbtule    schedule 06.07.2011
comment
Доступен ли метод InvokeGet в стандарте .net core / .net? - person Ramki; 25.02.2020
comment
Да, я переместил методы в новую библиотеку, чтобы сделать переносимую библиотеку в те времена. С тех пор он стал стандартом .net - person jbtule; 25.02.2020

Вы можете использовать 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));

Это работает, но я сомневаюсь, что это лучший вариант, и если вам нужен метод вызова, он может стать немного сложнее.

person João Angelo    schedule 27.06.2011

Если вы создаете подкласс DynamicObject, вы можете переопределить TrySetMember, чтобы сохранить свойства по имени в локальном словаре и переопределить TryGetIndex, чтобы их можно было извлечь, используя имя свойства в виде строки. Хороший пример этого есть на странице DynamicObject. в MSDN.

Это не самый быстрый способ сделать это, поскольку он не использует преимущества динамической диспетчеризации и сайтов вызова (и других вещей, которые я не совсем понимаю), как мог бы, но он будет работать. Это будет выглядеть так:

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"];
person Steve Wilkes    schedule 27.06.2011
comment
Дело в том, что я работаю в предположении, что я не контролирую динамический объект, который я потребляю - это может быть любой объект, реализующий IDynamicMetaObjectProvider. - person Jean Hominal; 27.06.2011

Это в основном вариант этого вопроса: Динамическое добавление участников в динамический объект, но вместо добавления вы хотите получить участников, поэтому просто измените привязку и подпись сайта вызова. Похоже, вы уже придумали, как сделать скоросшиватель.

person Dino Viehland    schedule 28.06.2011