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; }
    }
}
⚠️ **GitHub.com Fallback** ⚠️