New binding mechanism: Expression tracking

This commit is contained in:
2023-08-15 13:17:42 +02:00
parent 335433562a
commit b792639635
35 changed files with 971 additions and 311 deletions

View File

@@ -2,11 +2,12 @@
using System.Linq.Expressions;
using System.Reflection;
using TerminalUI.Controls;
using TerminalUI.ExpressionTrackers;
using TerminalUI.Traits;
namespace TerminalUI;
public sealed class Binding<TDataContext, TExpressionResult, TResult> : PropertyTrackerBase<TDataContext, TExpressionResult>
public sealed class Binding<TDataContext, TExpressionResult, TResult> : PropertyTrackerBase<TDataContext, TExpressionResult>, IDisposable
{
private readonly Func<TDataContext, TExpressionResult> _dataContextMapper;
private IView<TDataContext> _dataSourceView;
@@ -15,6 +16,7 @@ public sealed class Binding<TDataContext, TExpressionResult, TResult> : Property
private readonly Func<TExpressionResult, TResult> _converter;
private readonly TResult? _fallbackValue;
private IDisposableCollection? _propertySourceDisposableCollection;
private readonly string _parameterName;
public Binding(
IView<TDataContext> dataSourceView,
@@ -23,7 +25,7 @@ public sealed class Binding<TDataContext, TExpressionResult, TResult> : Property
PropertyInfo targetProperty,
Func<TExpressionResult, TResult> converter,
TResult? fallbackValue = default
) : base(() => dataSourceView.DataContext, dataSourceExpression)
) : base(dataSourceExpression)
{
ArgumentNullException.ThrowIfNull(dataSourceView);
ArgumentNullException.ThrowIfNull(dataSourceExpression);
@@ -37,14 +39,14 @@ public sealed class Binding<TDataContext, TExpressionResult, TResult> : Property
_converter = converter;
_fallbackValue = fallbackValue;
UpdateTrackers();
_parameterName = dataSourceExpression.Parameters[0].Name!;
Parameters.SetValue(_parameterName, dataSourceView.DataContext);
dataSourceView.PropertyChanged += View_PropertyChanged;
UpdateTargetProperty();
Update(true);
AddToSourceDisposables(propertySource);
dataSourceView.AddDisposable(this);
}
private void AddToSourceDisposables(object? propertySource)
@@ -60,18 +62,23 @@ public sealed class Binding<TDataContext, TExpressionResult, TResult> : Property
{
if (e.PropertyName != nameof(IView<TDataContext>.DataContext)) return;
UpdateTrackers();
UpdateTargetProperty();
Parameters.SetValue(_parameterName, _dataSourceView.DataContext);
Update(true);
}
protected override void Update(string propertyPath) => UpdateTargetProperty();
private void UpdateTargetProperty()
protected override void Update(bool couldCompute)
{
TResult value;
TResult? value;
try
{
value = _converter(_dataContextMapper(_dataSourceView.DataContext));
if (couldCompute)
{
value = _converter(_dataContextMapper(_dataSourceView.DataContext));
}
else
{
value = _fallbackValue;
}
}
catch
{
@@ -87,9 +94,9 @@ public sealed class Binding<TDataContext, TExpressionResult, TResult> : Property
}
}
public override void Dispose()
public void Dispose()
{
base.Dispose();
//base.Dispose();
_propertySourceDisposableCollection?.RemoveDisposable(this);
_dataSourceView.RemoveDisposable(this);
_dataSourceView.PropertyChanged -= View_PropertyChanged;

View File

@@ -27,11 +27,11 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
public TextBlock()
{
this.Bind(
/*this.Bind(
this,
dc => dc == null ? string.Empty : dc.ToString(),
tb => tb.Text
);
);*/
RerenderProperties.Add(nameof(Text));
RerenderProperties.Add(nameof(TextAlignment));

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
namespace TerminalUI;
@@ -32,14 +33,15 @@ public class EventLoop : IEventLoop
{
foreach (var action in _permanentQueue)
{
try
{
/*try
{*/
action();
}
/*}
catch (Exception e)
{
Debug.Fail(e.Message);
_logger.LogError(e, "Error while processing action in permanent queue");
}
}*/
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Linq.Expressions;
namespace TerminalUI.ExpressionTrackers;
public class BinaryTracker : ExpressionTrackerBase
{
private readonly IExpressionTracker _leftExpressionTracker;
private readonly IExpressionTracker _rightExpressionTracker;
private readonly Func<object?, object?, object?> _computer;
public BinaryTracker(
BinaryExpression binaryExpression,
IExpressionTracker leftExpressionTracker,
IExpressionTracker rightExpressionTracker)
{
_leftExpressionTracker = leftExpressionTracker;
_rightExpressionTracker = rightExpressionTracker;
ArgumentNullException.ThrowIfNull(leftExpressionTracker);
ArgumentNullException.ThrowIfNull(rightExpressionTracker);
SubscribeToValueChanges = false;
SubscribeToTracker(leftExpressionTracker);
SubscribeToTracker(rightExpressionTracker);
_computer = binaryExpression.NodeType switch
{
ExpressionType.Equal => (v1, v2) => Equals(v1, v2),
ExpressionType.NotEqual => (v1, v2) => !Equals(v1, v2),
_ => throw new NotImplementedException()
};
UpdateValueAndChangeTrackers();
}
protected override object? ComputeValue()
=> _computer(_leftExpressionTracker.GetValue(), _rightExpressionTracker.GetValue());
}

View File

@@ -0,0 +1,44 @@
using System.Linq.Expressions;
namespace TerminalUI.ExpressionTrackers;
public class ConditionalTracker : ExpressionTrackerBase
{
private readonly IExpressionTracker _testExpressionTracker;
private readonly IExpressionTracker _ifTrueExpressionTracker;
private readonly IExpressionTracker _ifFalseExpressionTracker;
public ConditionalTracker(
ConditionalExpression conditionalExpression,
IExpressionTracker testExpressionTracker,
IExpressionTracker ifTrueExpressionTracker,
IExpressionTracker ifFalseExpressionTracker)
{
ArgumentNullException.ThrowIfNull(conditionalExpression);
ArgumentNullException.ThrowIfNull(testExpressionTracker);
ArgumentNullException.ThrowIfNull(ifTrueExpressionTracker);
SubscribeToValueChanges = false;
_testExpressionTracker = testExpressionTracker;
_ifTrueExpressionTracker = ifTrueExpressionTracker;
_ifFalseExpressionTracker = ifFalseExpressionTracker;
SubscribeToTracker(testExpressionTracker);
SubscribeToTracker(ifTrueExpressionTracker);
SubscribeToTracker(ifFalseExpressionTracker);
UpdateValueAndChangeTrackers();
}
protected override object? ComputeValue()
{
var testValue = _testExpressionTracker.GetValue();
return testValue switch
{
true => _ifTrueExpressionTracker.GetValue(),
false => _ifFalseExpressionTracker.GetValue(),
_ => throw new NotSupportedException($"Conditional expression must evaluate to a boolean value, but {testValue} ({testValue.GetType().Name}) is not that.")
};
}
}

View File

@@ -0,0 +1,13 @@
namespace TerminalUI.ExpressionTrackers;
public class ConstantTracker : ExpressionTrackerBase
{
private readonly object? _value;
public ConstantTracker(object? value)
{
_value = value;
UpdateValueAndChangeTrackers();
}
protected override object? ComputeValue() => _value;
}

View File

@@ -0,0 +1,19 @@
using System.Collections.ObjectModel;
namespace TerminalUI.ExpressionTrackers;
public class ExpressionParameterTrackerCollection
{
private readonly Dictionary<string, object?> _values = new();
public ReadOnlyDictionary<string, object?> Values => new(_values);
public event Action<string, object?>? ValueChanged;
public void SetValue(string name, object? value)
{
ArgumentException.ThrowIfNullOrEmpty(name);
_values[name] = value;
ValueChanged?.Invoke(name, value);
}
}

View File

@@ -0,0 +1,82 @@
using System.Collections.Specialized;
using System.ComponentModel;
namespace TerminalUI.ExpressionTrackers;
public abstract class ExpressionTrackerBase : IExpressionTracker
{
private object? _currentValue;
public List<string> TrackedPropertyNames { get; } = new();
protected bool SubscribeToValueChanges { get; set; } = true;
public event Action<string>? PropertyChanged;
public event Action<bool>? Update;
public object? GetValue() => _currentValue;
protected abstract object? ComputeValue();
protected void SubscribeToTracker(IExpressionTracker? expressionTracker)
{
if (expressionTracker is null) return;
expressionTracker.Update += UpdateValueAndChangeTrackers;
}
protected void UpdateValueAndChangeTrackers() => UpdateValueAndChangeTrackers(true);
private void UpdateValueAndChangeTrackers(bool couldCompute)
{
if (SubscribeToValueChanges)
{
if (_currentValue is INotifyPropertyChanged oldNotifyPropertyChanged)
oldNotifyPropertyChanged.PropertyChanged -= OnPropertyChanged;
if (_currentValue is INotifyCollectionChanged collectionChanged)
collectionChanged.CollectionChanged -= OnCollectionChanged;
}
var useNull = false;
try
{
if (couldCompute)
{
_currentValue = ComputeValue();
if (SubscribeToValueChanges)
{
if (_currentValue is INotifyPropertyChanged notifyPropertyChanged)
notifyPropertyChanged.PropertyChanged += OnPropertyChanged;
if (_currentValue is INotifyCollectionChanged collectionChanged)
collectionChanged.CollectionChanged += OnCollectionChanged;
}
Update?.Invoke(true);
}
else
{
useNull = true;
}
}
catch (Exception e)
{
useNull = true;
}
if (useNull)
{
_currentValue = null;
Update?.Invoke(false);
}
}
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
=> UpdateValueAndChangeTrackers();
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is null) return;
if (TrackedPropertyNames.Contains(e.PropertyName))
{
PropertyChanged?.Invoke(e.PropertyName);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace TerminalUI.ExpressionTrackers;
public interface IExpressionTracker
{
List<string> TrackedPropertyNames { get; }
event Action<string>? PropertyChanged;
event Action<bool>? Update;
object? GetValue();
}

View File

@@ -0,0 +1,82 @@
using System.Linq.Expressions;
using System.Reflection;
namespace TerminalUI.ExpressionTrackers;
public sealed class MemberTracker : ExpressionTrackerBase
{
private readonly IExpressionTracker? _parentTracker;
private readonly string _memberName;
private readonly Func<object?> _valueProvider;
public MemberTracker(MemberExpression memberExpression, IExpressionTracker? parentTracker)
{
ArgumentNullException.ThrowIfNull(memberExpression);
_parentTracker = parentTracker;
if (parentTracker is not null)
{
parentTracker.PropertyChanged += propertyName =>
{
if (propertyName == _memberName)
{
UpdateValueAndChangeTrackers();
}
};
}
if (memberExpression.Member is PropertyInfo propertyInfo)
{
_memberName = propertyInfo.Name;
parentTracker?.TrackedPropertyNames.Add(propertyInfo.Name);
if (propertyInfo.GetMethod is { } getMethod)
{
_valueProvider = () => CallPropertyInfo(propertyInfo);
}
else
{
throw new NotSupportedException(
$"Try to get value of a property without a getter: {propertyInfo.Name} in {propertyInfo.DeclaringType?.Name ?? "<null>"}."
);
}
}
else if (memberExpression.Member is FieldInfo fieldInfo)
{
_memberName = fieldInfo.Name;
parentTracker?.TrackedPropertyNames.Add(fieldInfo.Name);
_valueProvider = () => CallFieldInfo(fieldInfo);
}
else
{
throw new NotSupportedException($"Could not determine source type of expression {memberExpression} with parent {parentTracker}");
}
SubscribeToTracker(parentTracker);
UpdateValueAndChangeTrackers();
}
private object? CallPropertyInfo(PropertyInfo propertyInfo)
{
var obj = _parentTracker?.GetValue();
if (obj is null && !propertyInfo.GetMethod!.IsStatic) return null;
return propertyInfo.GetValue(_parentTracker?.GetValue());
}
private object? CallFieldInfo(FieldInfo fieldInfo)
{
var obj = _parentTracker?.GetValue();
if (obj is null && !fieldInfo.IsStatic) return null;
return fieldInfo.GetValue(_parentTracker?.GetValue());
}
protected override object? ComputeValue()
{
var v = _valueProvider();
return v;
}
}

View File

@@ -0,0 +1,40 @@
using System.Linq.Expressions;
using System.Reflection;
namespace TerminalUI.ExpressionTrackers;
public sealed class MethodCallTracker : ExpressionTrackerBase
{
private readonly MethodInfo _method;
private readonly IExpressionTracker? _objectTracker;
private readonly List<IExpressionTracker> _argumentTrackers;
public MethodCallTracker(
MethodCallExpression methodCallExpression,
IExpressionTracker? objectTracker,
IEnumerable<IExpressionTracker> argumentTrackers)
{
_method = methodCallExpression.Method;
_objectTracker = objectTracker;
_argumentTrackers = argumentTrackers.ToList();
if (objectTracker is not null)
{
SubscribeToTracker(objectTracker);
}
foreach (var expressionTracker in _argumentTrackers)
{
SubscribeToTracker(expressionTracker);
}
UpdateValueAndChangeTrackers();
}
protected override object? ComputeValue()
{
var obj = _objectTracker?.GetValue();
if (obj is null && !_method.IsStatic) return null;
return _method.Invoke(obj, _argumentTrackers.Select(t => t.GetValue()).ToArray());
}
}

View File

@@ -0,0 +1,31 @@
using System.Linq.Expressions;
namespace TerminalUI.ExpressionTrackers;
public sealed class ParameterTracker : ExpressionTrackerBase
{
private readonly ExpressionParameterTrackerCollection _trackerCollection;
private readonly string _parameterName;
public ParameterTracker(
ParameterExpression parameterExpression,
ExpressionParameterTrackerCollection trackerCollection,
string parameterName)
{
_trackerCollection = trackerCollection;
_parameterName = parameterName;
trackerCollection.ValueChanged += TrackerCollectionOnValueChanged;
UpdateValueAndChangeTrackers();
}
private void TrackerCollectionOnValueChanged(string parameterName, object? newValue)
=> UpdateValueAndChangeTrackers();
protected override object? ComputeValue()
{
_trackerCollection.Values.TryGetValue(_parameterName, out var v);
return v;
}
}

View File

@@ -0,0 +1,46 @@
using System.Linq.Expressions;
namespace TerminalUI.ExpressionTrackers;
public class UnaryTracker : ExpressionTrackerBase
{
private readonly IExpressionTracker _operandTracker;
private readonly Func<object?, object?> _operator;
public UnaryTracker(UnaryExpression unaryExpression, IExpressionTracker operandTracker)
{
_operandTracker = operandTracker;
ArgumentNullException.ThrowIfNull(unaryExpression);
ArgumentNullException.ThrowIfNull(operandTracker);
SubscribeToValueChanges = false;
_operator = unaryExpression.NodeType switch
{
ExpressionType.Negate => Negate,
ExpressionType.Not => o => o is bool b ? !b : null,
ExpressionType.Convert => o => o,
_ => throw new NotSupportedException($"Unary expression of type {unaryExpression.NodeType} is not supported.")
};
SubscribeToTracker(operandTracker);
UpdateValueAndChangeTrackers();
}
private static object? Negate(object? source)
{
if (source is null) return null;
return source switch
{
int i => -i,
long l => -l,
float f => -f,
double d => -d,
decimal d => -d,
_ => throw new NotSupportedException($"Unary negation is not supported for type {source.GetType().Name}.")
};
}
protected override object? ComputeValue() => _operator(_operandTracker.GetValue());
}

View File

@@ -4,7 +4,7 @@ using TerminalUI.Controls;
namespace TerminalUI.Extensions;
public static class Binding
public static class BindingExtensions
{
public static Binding<TDataContext, TResult, TResult> Bind<TView, TDataContext, TResult>(
this TView targetView,

View File

@@ -28,7 +28,7 @@ public static class ViewExtensions
public static TItem WithPropertyChangedHandler<TItem, TExpressionResult>(
this TItem dataSource,
Expression<Func<TItem, TExpressionResult>> dataSourceExpression,
Action<string, bool, TExpressionResult> handler)
Action<bool, TExpressionResult> handler)
{
new PropertyChangedHandler<TItem, TExpressionResult>
(

View File

@@ -1,149 +0,0 @@
using System.ComponentModel;
namespace TerminalUI;
public interface IPropertyChangeTracker : IDisposable
{
string Name { get; }
string Path { get; }
Dictionary<string, IPropertyChangeTracker> Children { get; }
}
public abstract class PropertyChangeTrackerBase : IPropertyChangeTracker
{
public string Name { get; }
public string Path { get; }
public Dictionary<string, IPropertyChangeTracker> Children { get; } = new();
protected PropertyChangeTrackerBase(string name, string path)
{
Name = name;
Path = path;
}
public virtual void Dispose()
{
foreach (var propertyChangeTracker in Children.Values)
{
propertyChangeTracker.Dispose();
}
}
}
public class PropertyChangeTracker : PropertyChangeTrackerBase
{
private readonly PropertyTrackTreeItem _propertyTrackTreeItem;
private readonly INotifyPropertyChanged _target;
private readonly IEnumerable<string> _propertiesToListen;
private readonly Action<string> _updateBinding;
public PropertyChangeTracker(
string name,
string path,
PropertyTrackTreeItem propertyTrackTreeItem,
INotifyPropertyChanged target,
IEnumerable<string> propertiesToListen,
Action<string> updateBinding) : base(name, path)
{
_propertyTrackTreeItem = propertyTrackTreeItem;
_target = target;
_propertiesToListen = propertiesToListen;
_updateBinding = updateBinding;
target.PropertyChanged += Target_PropertyChanged;
}
private void Target_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
var propertyName = e.PropertyName;
if (propertyName is null || !_propertiesToListen.Contains(propertyName))
{
return;
}
Children.Remove(propertyName);
var newChild = PropertyChangeHelper.CreatePropertyTracker(
Path,
_propertyTrackTreeItem.Children[propertyName],
_target.GetType().GetProperty(propertyName)?.GetValue(_target),
_updateBinding
);
if (newChild is not null)
{
Children.Add(propertyName, newChild);
}
_updateBinding(propertyName);
}
public override void Dispose()
{
_target.PropertyChanged -= Target_PropertyChanged;
base.Dispose();
}
}
public class NonSubscriberPropertyChangeTracker : PropertyChangeTrackerBase
{
public NonSubscriberPropertyChangeTracker(string name, string path) : base(name, path)
{
}
}
public class PropertyTrackTreeItem
{
public string Name { get; }
public Dictionary<string, PropertyTrackTreeItem> Children { get; } = new();
public PropertyTrackTreeItem(string name)
{
Name = name;
}
}
public static class PropertyChangeHelper
{
internal static IPropertyChangeTracker? CreatePropertyTracker(
string? path,
PropertyTrackTreeItem propertyTrackTreeItem,
object? obj,
Action<string> updateBinding
)
{
if (obj is null) return null;
path = path is null ? propertyTrackTreeItem.Name : path + "." + propertyTrackTreeItem.Name;
IPropertyChangeTracker tracker = obj is INotifyPropertyChanged notifyPropertyChanged
? new PropertyChangeTracker(
propertyTrackTreeItem.Name,
path,
propertyTrackTreeItem,
notifyPropertyChanged,
propertyTrackTreeItem.Children.Keys,
updateBinding
)
: new NonSubscriberPropertyChangeTracker(
propertyTrackTreeItem.Name,
path);
foreach (var (propertyName, trackerTreeItem) in propertyTrackTreeItem.Children)
{
var childTracker = CreatePropertyTracker(
path,
trackerTreeItem,
obj.GetType().GetProperty(propertyName)?.GetValue(obj),
updateBinding
);
if (childTracker is not null)
{
tracker.Children.Add(propertyName, childTracker);
}
}
return tracker;
}
}

View File

@@ -2,44 +2,48 @@
namespace TerminalUI;
public sealed class PropertyChangedHandler<TItem, TExpressionResult> : PropertyTrackerBase<TItem, TExpressionResult>, IDisposable
public sealed class PropertyChangedHandler<TItem, TExpressionResult> : PropertyTrackerBase<TItem, TExpressionResult>
{
private readonly TItem _dataSource;
private readonly Action<string, bool, TExpressionResult?> _handler;
private readonly PropertyTrackTreeItem? _propertyTrackTreeItem;
private readonly Action<bool, TExpressionResult?> _handler;
private readonly Func<TItem, TExpressionResult> _propertyValueGenerator;
public PropertyChangedHandler(
TItem dataSource,
Expression<Func<TItem, TExpressionResult>> dataSourceExpression,
Action<string, bool, TExpressionResult?> handler
) : base(() => dataSource, dataSourceExpression)
Action<bool, TExpressionResult?> handler
) : base(dataSourceExpression!)
{
_dataSource = dataSource;
_handler = handler;
ArgumentNullException.ThrowIfNull(dataSource);
ArgumentNullException.ThrowIfNull(dataSourceExpression);
ArgumentNullException.ThrowIfNull(handler);
_dataSource = dataSource;
_handler = handler;
Parameters.SetValue(dataSourceExpression.Parameters[0].Name!, dataSource);
_propertyTrackTreeItem = CreateTrackingTree(dataSourceExpression);
_propertyValueGenerator = dataSourceExpression.Compile();
UpdateTrackers();
Update(true);
}
protected override void Update(string propertyPath)
protected override void Update(bool couldCompute)
{
TExpressionResult? value = default;
var parsed = false;
try
{
value = _propertyValueGenerator(_dataSource);
parsed = true;
if (couldCompute)
{
value = _propertyValueGenerator(_dataSource);
parsed = true;
}
}
catch
{
}
_handler(propertyPath, parsed, value);
_handler(parsed, value);
}
}

View File

@@ -1,145 +1,92 @@
using System.Linq.Expressions;
using System.Reflection;
using TerminalUI.ExpressionTrackers;
namespace TerminalUI;
public abstract class PropertyTrackerBase<TSource, TExpressionResult> : IDisposable
public abstract class PropertyTrackerBase<TSource, TExpressionResult>
{
private readonly Func<TSource?> _source;
protected PropertyTrackTreeItem? PropertyTrackTreeItem { get; }
protected IPropertyChangeTracker? PropertyChangeTracker { get; private set; }
private readonly IExpressionTracker _tracker;
protected ExpressionParameterTrackerCollection Parameters { get; } = new();
protected PropertyTrackerBase(
Func<TSource?> source,
Expression<Func<TSource?, TExpressionResult>> dataSourceExpression)
protected PropertyTrackerBase(Expression<Func<TSource?, TExpressionResult>> dataSourceExpression)
{
ArgumentNullException.ThrowIfNull(dataSourceExpression);
_source = source;
PropertyTrackTreeItem = CreateTrackingTree(dataSourceExpression);
_tracker = FindReactiveProperties(dataSourceExpression.Body, Parameters);
_tracker.Update += Update;
}
protected PropertyTrackTreeItem? CreateTrackingTree(Expression<Func<TSource?, TExpressionResult>> dataContextExpression)
private IExpressionTracker FindReactiveProperties(Expression? expression, ExpressionParameterTrackerCollection parameters)
{
var properties = new List<string>();
FindReactiveProperties(dataContextExpression, properties);
if (properties.Count > 0)
if (expression is ConditionalExpression conditionalExpression)
{
var rootItem = new PropertyTrackTreeItem(null!);
foreach (var property in properties)
{
var pathParts = property.Split('.');
var currentItem = rootItem;
for (var i = 0; i < pathParts.Length; i++)
{
if (!currentItem.Children.TryGetValue(pathParts[i], out var child))
{
child = new PropertyTrackTreeItem(pathParts[i]);
currentItem.Children.Add(pathParts[i], child);
}
var testTracker = FindReactiveProperties(conditionalExpression.Test, parameters);
var trueTracker = FindReactiveProperties(conditionalExpression.IfTrue, parameters);
var falseTracker = FindReactiveProperties(conditionalExpression.IfFalse, parameters);
currentItem = child;
}
}
return rootItem;
}
return null;
}
private string? FindReactiveProperties(Expression? expression, List<string> properties)
{
if (expression is null) return "";
if (expression is LambdaExpression lambdaExpression)
{
SavePropertyPath(FindReactiveProperties(lambdaExpression.Body, properties));
}
else if (expression is ConditionalExpression conditionalExpression)
{
SavePropertyPath(FindReactiveProperties(conditionalExpression.Test, properties));
SavePropertyPath(FindReactiveProperties(conditionalExpression.IfTrue, properties));
SavePropertyPath(FindReactiveProperties(conditionalExpression.IfFalse, properties));
return new ConditionalTracker(
conditionalExpression,
testTracker,
trueTracker,
falseTracker);
}
else if (expression is MemberExpression memberExpression)
{
IExpressionTracker? parentExpressionTracker = null;
if (memberExpression.Expression is not null)
{
FindReactiveProperties(memberExpression.Expression, properties);
if (FindReactiveProperties(memberExpression.Expression, properties) is { } path
&& memberExpression.Member is PropertyInfo dataContextPropertyInfo)
{
path += "." + memberExpression.Member.Name;
return path;
}
parentExpressionTracker = FindReactiveProperties(memberExpression.Expression, parameters);
}
return new MemberTracker(memberExpression, parentExpressionTracker);
}
else if (expression is MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Object is
{
NodeType:
not ExpressionType.Parameter
and not ExpressionType.Constant
} methodObject)
IExpressionTracker? objectTracker = null;
if (methodCallExpression.Object is { } methodObject)
{
SavePropertyPath(FindReactiveProperties(methodObject, properties));
objectTracker = FindReactiveProperties(methodObject, parameters);
}
var argumentTrackers = new List<IExpressionTracker>(methodCallExpression.Arguments.Count);
foreach (var argument in methodCallExpression.Arguments)
{
SavePropertyPath(FindReactiveProperties(argument, properties));
var argumentTracker = FindReactiveProperties(argument, parameters);
argumentTrackers.Add(argumentTracker);
}
return new MethodCallTracker(methodCallExpression, objectTracker, argumentTrackers);
}
else if (expression is BinaryExpression binaryExpression)
{
SavePropertyPath(FindReactiveProperties(binaryExpression.Left, properties));
SavePropertyPath(FindReactiveProperties(binaryExpression.Right, properties));
var leftTracker = FindReactiveProperties(binaryExpression.Left, parameters);
var rightTracker = FindReactiveProperties(binaryExpression.Right, parameters);
return new BinaryTracker(binaryExpression, leftTracker, rightTracker);
}
else if (expression is UnaryExpression unaryExpression)
{
return FindReactiveProperties(unaryExpression.Operand, properties);
var operandTracker = FindReactiveProperties(unaryExpression.Operand, parameters);
return new UnaryTracker(unaryExpression, operandTracker);
}
else if (expression is ParameterExpression parameterExpression)
{
if (parameterExpression.Type == typeof(TSource))
if (parameterExpression.Name is { } name)
{
return "";
return new ParameterTracker(parameterExpression, parameters, name);
}
}
return null;
void SavePropertyPath(string? path)
else if (expression is ConstantExpression constantExpression)
{
if (path is null) return;
path = path.TrimStart('.');
properties.Add(path);
return new ConstantTracker(constantExpression.Value);
}
/*else if (expression is not ConstantExpression)
{
Debug.Assert(false, "Unknown expression type " + expression.GetType());
}*/
throw new NotSupportedException();
}
protected void UpdateTrackers()
{
if (PropertyChangeTracker is not null)
{
PropertyChangeTracker.Dispose();
}
if (PropertyTrackTreeItem is not null)
{
PropertyChangeTracker = PropertyChangeHelper.CreatePropertyTracker(
null,
PropertyTrackTreeItem,
_source(),
Update
);
}
}
protected abstract void Update(string propertyPath);
public virtual void Dispose() => PropertyChangeTracker?.Dispose();
protected abstract void Update(bool couldCompute);
}