If you can change the calling code, it would be simpler to pass an
Action<T>
to update the property value.
Otherwise, you'll need to extract the target instance from the expression. But be warned, this is quite fragile.
public static class ExpressionExtensions
{
private static object GetSourceObject(MemberExpression body)
{
if (body.Expression is ConstantExpression c)
{
return c.Value;
}
if (body.Expression is MemberExpression m && m.Expression is ConstantExpression mc)
{
if (m.Member is FieldInfo f) return f.GetValue(mc.Value);
if (m.Member is PropertyInfo p) return p.GetValue(mc.Value);
}
throw new NotSupportedException("Invalid expression: " + body);
}
public static (object instance, PropertyInfo property) ExtractProperty(LambdaExpression expression)
{
if (expression is null) throw new ArgumentNullException(nameof(expression));
if (expression.Body is MemberExpression body && body.Member is PropertyInfo property)
{
object instance = GetSourceObject(body);
return (instance, property);
}
throw new NotSupportedException("Invalid expression: " + expression);
}
}
public static void SetPropertyAfterXseconds<T>(Expression<Func<T>> propertyToSet, T valueToSet = default)
{
var (instance, property) = ExpressionExtensions.ExtractProperty(propertyToSet);
property.SetValue(instance, valueToSet);
}
This will support two types of calls:
public class Foo
{
public string Bar { get; set; }
public void DoIt()
{
SomeClass.SetPropertyAfterXseconds(() => Bar);
}
}
Foo x = new Foo { Bar = "Baz" };
SomeClass.SetPropertyAfterXseconds(() => x.Bar);
For option 1, the body expression will be a
ConstantExpression
pointing to
this
.
For option 2, the body expression will be a
MemberExpression
pointing to a field on a compiler-generated closure class.