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

@@ -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);
}