ObservableAsProperty - kswoll/someta GitHub Wiki
ReactiveUI provides a Fody extension (that I originally authored), but using this framework, you can just do it yourself, if you wanted. It provides a good example of the sort of extensions you can author with minimal fuss:
public class ObservableAsPropertyAttribute : Attribute, IPropertyGetInterceptor, IStateExtensionPoint,
IInstanceInitializer
{
public InjectedField<object> Helper { get; set; }
private Delegate getValue;
public void Initialize(object instance, MemberInfo member)
{
// Use expression trees to create a delegate of the form:
//
// Func<object, PropertyType> = x => ((ObservableAsPropertyHelper<PropertyType>)x).Value
//
// The first type parameter represents the ObservableAsPropertyHelper<> instance
// The seconds type parameter is the property type of the property the attribute is associated with
// You *could* just use simple reflection, but this solution is more performant
var propertyInfo = (PropertyInfo)member;
var observableAsPropertyHelperType = typeof(ObservableAsPropertyHelper<>).MakeGenericType(propertyInfo.PropertyType);
var valueProperty = observableAsPropertyHelperType.GetProperty("Value");
var instanceParameter = Expression.Parameter(typeof(object));
var castedInstance = Expression.Convert(instanceParameter, observableAsPropertyHelperType);
var body = Expression.MakeMemberAccess(castedInstance, valueProperty);
var delegateType = typeof(Func<,>).MakeGenericType(typeof(object), propertyInfo.PropertyType);
var lambda = Expression.Lambda(delegateType, body, instanceParameter);
getValue = lambda.Compile();
}
public object GetPropertyValue(PropertyInfo propertyInfo, object instance, Func<object> getter)
{
var helper = Helper.GetValue(instance);
return getValue.DynamicInvoke(helper);
}
}
Here's the equivalent implementation to ObservableAsPropertyExtensions:
public static class ObservabeAsPropertyExtensions
{
public static ObservableAsPropertyHelper<TRet> ToPropertyEx<TObj, TRet>(this IObservable<TRet> @this, TObj source, Expression<Func<TObj, TRet>> property, TRet initialValue = default(TRet), bool deferSubscription = false, IScheduler scheduler = null)
where TObj : ReactiveObject
{
var result = @this.ToProperty(source, property, initialValue, deferSubscription, scheduler);
// Now assign the field via reflection.
var propertyInfo = property.GetPropertyInfo();
var extensionPoint = propertyInfo.GetExtensionPoint<ObservableAsPropertyAttribute>();
extensionPoint.Helper.SetValue(source, result);
return result;
}
private static PropertyInfo GetPropertyInfo(this LambdaExpression expression)
{
var current = expression.Body;
if (current is UnaryExpression unary)
{
current = unary.Operand;
}
var call = (MemberExpression)current;
return (PropertyInfo)call.Member;
}
}
Usage:
class Program
{
static void Main(string[] args)
{
var subject = new Subject<string>();
var o = new TestClass();
subject.ToPropertyEx(o, x => x.StringProperty);
Console.WriteLine($"Initial value: {o.StringProperty}");
subject.OnNext("first value");
Console.WriteLine($"Next value: {o.StringProperty}");
}
public class TestClass : ReactiveObject
{
[ObservableAsProperty]
public string StringProperty { get; }
}
}