Reputation: 14912
Not a lot of experience with Expression
s, 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.Expression
1[System.Func
2[Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.Sensors,Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.JsonSensor1[System.Double]]]' to type 'System.Linq.Expressions.Expression
1[System.Func1[Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.JsonSensor
1[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
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