using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
public interface IReactiveValue<T> : INotifyPropertyChanged
public class ShallowRef<T>(T inner) : IReactiveValue<T>
public event PropertyChangedEventHandler? PropertyChanged;
PropertyChanged?.Invoke(this, new(null));
public class ComputedValue<T> : IReactiveValue<T>
public static ComputedValueBuilder<T> Builder() => new();
public event PropertyChangedEventHandler? PropertyChanged;
private T? _cache = default;
private bool _stale = true;
private readonly Delegate _callFunc;
internal ComputedValue(Expression expr, IEnumerable<INotifyPropertyChanged> dependencies)
foreach (INotifyPropertyChanged dependency in dependencies)
dependency.PropertyChanged += (o, e) => Invalidate();
_callFunc = Expression.Lambda(expr).Compile();
public T Compute() => (T)_callFunc.DynamicInvoke()!;
public void Invalidate() => _stale = true;
public readonly struct ComputedValueBuilder<R>
private readonly Type[] _parameterTypes;
private readonly Expression[] _parameterExpressions;
private readonly INotifyPropertyChanged[] _dependencyProviders;
public ComputedValueBuilder()
_parameterExpressions = [];
_dependencyProviders = [];
private ComputedValueBuilder(
Expression[] parameterExpressions,
INotifyPropertyChanged[] dependencyProviders
_parameterTypes = parameterTypes;
_parameterExpressions = parameterExpressions;
_dependencyProviders = dependencyProviders;
public readonly ComputedValueBuilder<R> WithDependency<T>(IReactiveValue<T> value)
return new ComputedValueBuilder<R>(
[.. _parameterTypes, typeof(T)],
.. _parameterExpressions,
Expression.Constant(value),
nameof(IReactiveValue<T>.Value)
[.. _dependencyProviders, value]);
public readonly ComputedValue<R> Build(Delegate func)
if (func.GetType() != Expression.GetDelegateType([.. _parameterTypes, typeof(R)]))
throw new Exception("type mismatch in ComputedValueBuilder");
var call = func.Target == null
? Expression.Call(func.Method, _parameterExpressions)
: Expression.Call(Expression.Constant(func.Target), func.Method, _parameterExpressions);
return new(call, _dependencyProviders);
public ShallowRef<int> MyVal { get; }
public ComputedValue<int> MyComputed { get; }
MyComputed = ComputedValue<int>.Builder()
.Build((int myVal) => myVal * 2);
public static void Main()
Console.WriteLine($"{test.MyVal.Value} * 2 = {test.MyComputed.Value}");
Console.WriteLine($"{test.MyVal.Value} * 2 = {test.MyComputed.Value}");
Console.WriteLine($"{test.MyVal.Value} * 2 = {test.MyComputed.Value}");
Console.WriteLine($"{test.MyVal.Value} * 2 = {test.MyComputed.Value}");