bas
bas

Reputation: 14912

Create Expression that accesses a property

I have a following class:

internal class Sensors
{
    public JsonSensor<double> IOPcwFlSpr { get; set; } = new JsonSensor<double>();
 }

internal class JsonSensor<TType> : IJsonSensor
{
    public TType Value { get; set; }
}

I want to build an expression that retrieves that property.

private static readonly List < PropertyInfo > Properties;

static SensorFactory() {
    Properties = typeof(Json.Sensors).GetProperties().ToList();
}

public void Test(Json.Sensors jsonUpdate) {
    foreach(var property in Properties) {
        var getterMethodInfo = property.GetGetMethod();
        var parameterExpression = Expression.Parameter(jsonUpdate.GetType(), "x");
        var callExpression = Expression.Call(parameterExpression, getterMethodInfo);

        var lambda = Expression.Lambda < Func < JsonSensor < double >>> (callExpression);
        var r = lambda.Compile().Invoke();
    }
}

This throws:

System.InvalidOperationException : variable 'x' of type 'Sensors' referenced from scope '', but it is not defined

Which makes sense, because I never assigned 'x' with an actual object. How do I add the 'parameter object'?

Upvotes: 4

Views: 103

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1062590

The key when using expression trees like this is to compile it once using a parameter (ParameterExpression), creating a Func<Foo,Bar> that takes your input (Foo) and returns whatever you wanted (Bar). Then reuse that compiled delegate many times, with different objects.

I can't see exactly what you're trying to do, but I'm guessing it would be something like:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;


namespace Json
{
    static class P
    {
        static void Main()
        {
            var obj = new Sensors { IOPcwFlSpr = { Value = 42.5 }, Whatever = { Value = 9 } };

            foreach(var pair in SomeUtil.GetSensors(obj))
            {
                Console.WriteLine($"{pair.Name}: {pair.Value}");
            }
        }
    }

    public class Sensors
    {
        public JsonSensor<double> IOPcwFlSpr { get; set; } = new JsonSensor<double>();
        public JsonSensor<int> Whatever { get; set; } = new JsonSensor<int>();
    }

    public interface IJsonSensor
    {
        public string Value { get; }
    }
    public class JsonSensor<TType> : IJsonSensor
    {
        public TType Value { get; set; }
        string IJsonSensor.Value => Convert.ToString(Value);
    }

    public static class SomeUtil
    {
        private static readonly (string name, Func<Sensors, IJsonSensor> accessor)[] s_accessors
            = Array.ConvertAll(
                typeof(Sensors).GetProperties(BindingFlags.Instance | BindingFlags.Public),
                prop => (prop.Name, Compile(prop)));

        public static IEnumerable<(string Name, string Value)> GetSensors(Sensors obj)
        {
            foreach (var acc in s_accessors)
                yield return (acc.name, acc.accessor(obj).Value);
        }
        private static Func<Sensors, IJsonSensor> Compile(PropertyInfo property)
        {
            var parameterExpression = Expression.Parameter(typeof(Json.Sensors), "x");
            Expression body = Expression.Property(parameterExpression, property);
            body = Expression.Convert(body, typeof(IJsonSensor));
            var lambda = Expression.Lambda<Func<Json.Sensors, IJsonSensor>>(body, parameterExpression);
            return lambda.Compile();
        }
    }
}

Upvotes: 4

Related Questions