bas
bas

Reputation: 14912

Building expression for all properties of class

Not a lot of experience with Expressions, and having a hard time understanding the bigger picture.

I have a class which defines a large amount of properties. Instead of doing a lot of dumb type work, I am trying to use reflection/expressions to evaluate these properties for me.

A short sample of the class:

[Function(Name = "sensors")]
internal class Sensors
{
    [CabinetDoubleSensor(SensorType = SensorType.Temperature, Precision = 3)]
    [JsonProperty(PropertyName = "IO_PCW_FL_SPR")]
    public JsonSensor<double> IOPcwFlSpr { get; set; } = new JsonSensor<double>();

    [CabinetDoubleSensor(SensorType = SensorType.Temperature, Precision = 3)]
    [JsonProperty(PropertyName = "IO_PCW_RL_SPR")]
    public JsonSensor<double> IOPcwRlSpr { get; set; } = new JsonSensor<double>();

    // 100+ sensor definitions below
}

I want to evaluate all properties and store them in a list as follows:

    public IEnumerable<ISensor> UpdateSensors(Json.Sensors jsonUpdate)
    {
        // To test if it works, but clearly, I do NOT want to list all sensor evaluations here!
        UpdateSensor(() => jsonUpdate.IOPcwFlSpr);

        return SensorMap.Values.ToList();
    }

    private void UpdateSensor(Expression<Func<JsonSensor<double>>> propertySelector)
    {
        if (propertySelector.Body is MemberExpression expression)
        {
            var compiledExpression = propertySelector.Compile();
            var jsonSensor = compiledExpression.Invoke();

            var name = expression.Member.Name;
            var cabinetSensor = SensorMap[name];
            cabinetSensor.Value = jsonSensor.Value.ToString($"F{cabinetSensor.Precision}");
        }
    }

So then the part where I am stuck. As said, I don't want to call UpdateSensor(() => jsonUpdate.SensorName 100+ times. So I am trying to find a way to construct that lambda expression myself.

    private static readonly List<PropertyInfo> Properties;

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

    public IEnumerable<ISensor> Test(Json.Sensors jsonUpdate)
    {
        foreach (var property in Properties)
        {
            var getterMethodInfo = property.GetGetMethod();
            var parameterExpression = Expression.Parameter(jsonUpdate.GetType());
            var getterCall = Expression.Call(parameterExpression, getterMethodInfo);

            UnaryExpression castToObject = Expression.Convert(getterCall, typeof(JsonSensor<double>));
            var lambda = (Expression<Func<JsonSensor<double>>>)Expression.Lambda(castToObject, parameterExpression);

            UpdateSensor(lambda);
        }

        // not relevant
        return null;
    }

The cast is illegal:

System.InvalidCastException: 'Unable to cast object of type 'System.Linq.Expressions.Expression1[System.Func2[Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.Sensors,Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.JsonSensor1[System.Double]]]' to type 'System.Linq.Expressions.Expression1[System.Func1[Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.JsonSensor1[System.Double]]]'.'

I think (/hope) I am close, but I don't know how to get the Expression<Func<JsonSensor<double>>> as return value.

Upvotes: 1

Views: 91

Answers (1)

Krzysztof
Krzysztof

Reputation: 16140

Actually there are few problems in your code.

Exception itself is thrown, because you provide one parameter to Lambda method, and this way it will produce Func<T1, T2>. Func<T> doesn't accept any parameter, so you should call Expression.Lambda(castToObject).

Regardless, you should probably change it to Func<Sensors, JsonSensor<double>>, otherwise you'll need to wrap jsonUpdate as constant inside lambda.

Here is example of adjusted UpdateSensor and Test methods:

private static void UpdateSensor(Sensors jsonUpdate, Expression<Func<Sensors, JsonSensor<double>>> propertySelector)
{
    if (propertySelector.Body is MemberExpression expression)
    {
        var compiledExpression = propertySelector.Compile();
        // Signature was changed and jsonUpdate is not compiled into lambda; we need to pass reference
        var jsonSensor = compiledExpression.Invoke(jsonUpdate);

        var name = expression.Member.Name;
        var cabinetSensor = SensorMap[name];
        cabinetSensor.Value = jsonSensor.Value.ToString($"F{cabinetSensor.Precision}");
    }
}

public IEnumerable<Sensor> Test(Sensors jsonUpdate)
{
    foreach (var property in Properties)
    {
        var parameterExpression = Expression.Parameter(jsonUpdate.GetType());
        // You don't need call or GetMethod, you need to access Property
        var propertyCall = Expression.Property(parameterExpression, property);

        // Cast is redundant, and if you add it UpdateSensor will do nothing
        // UnaryExpression castToObject = Expression.Convert(propertyCall, typeof(JsonSensor<double>));
        var lambda = Expression.Lambda<Func<Sensors, JsonSensor<double>>>(propertyCall, parameterExpression);

        UpdateSensor(jsonUpdate, lambda);
    }

    // not relevant
    return null;
}

Upvotes: 1

Related Questions