Skip to content

Commit

Permalink
Initial reboot of typed bindings feature.
Browse files Browse the repository at this point in the history
Following on from #5510,
  • Loading branch information
grokys committed Aug 3, 2022
1 parent 7211f03 commit 70d64e6
Show file tree
Hide file tree
Showing 15 changed files with 3,046 additions and 0 deletions.
81 changes: 81 additions & 0 deletions src/Avalonia.Base/Data/Core/Parsers/ExpressionChainVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Avalonia.Data.Core.Parsers
{
public class ExpressionChainVisitor<TIn> : ExpressionVisitor
{
private readonly LambdaExpression _rootExpression;
private readonly List<Func<TIn, object>> _links = new();
private Expression? _head;

public ExpressionChainVisitor(LambdaExpression expression)
{
_rootExpression = expression;
}

public static Func<TIn, object>[] Build<TOut>(Expression<Func<TIn, TOut>> expression)
{
var visitor = new ExpressionChainVisitor<TIn>(expression);
visitor.Visit(expression);
return visitor._links.ToArray();
}

protected override Expression VisitBinary(BinaryExpression node)
{
var result = base.VisitBinary(node);
if (node.Left == _head)
_head = node;
return result;
}

protected override Expression VisitMember(MemberExpression node)
{
var result = base.VisitMember(node);

if (node.Expression is not null &&
node.Expression == _head &&
node.Expression.Type.IsValueType == false)
{
var link = Expression.Lambda<Func<TIn, object>>(node.Expression, _rootExpression.Parameters);
_links.Add(link.Compile());
_head = node;
}

return result;
}

protected override Expression VisitMethodCall(MethodCallExpression node)
{
var result = base.VisitMethodCall(node);

if (node.Object is not null &&
node.Object == _head &&
node.Type.IsValueType == false)
{
var link = Expression.Lambda<Func<TIn, object>>(node.Object, _rootExpression.Parameters);
_links.Add(link.Compile());
_head = node;
}

return result;
}

protected override Expression VisitParameter(ParameterExpression node)
{
if (node == _rootExpression.Parameters[0])
_head = node;
return base.VisitParameter(node);
}

protected override Expression VisitUnary(UnaryExpression node)
{
var result = base.VisitUnary(node);
if (node.Operand == _head)
_head = node;
return result;
}
}

}
168 changes: 168 additions & 0 deletions src/Avalonia.Base/Data/Core/TypedBindingExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using System;
using System.Linq.Expressions;
using Avalonia.Data.Core.Parsers;

#nullable enable

namespace Avalonia.Data.Core
{
/// <summary>
/// Provides factory methods for creating <see cref="TypedBindingExpression{TIn, TOut}"/>
/// objects from C# lambda expressions.
/// </summary>
public static class TypedBindingExpression
{
public static TypedBindingExpression<TIn, TOut> OneWay<TIn, TOut>(
TIn root,
Expression<Func<TIn, TOut>> read,
Optional<TOut> fallbackValue = default)
where TIn : class
{
return new TypedBindingExpression<TIn, TOut>(
new Single<TIn>(root),
read.Compile(),
null,
ExpressionChainVisitor<TIn>.Build(read),
fallbackValue);
}

public static TypedBindingExpression<TIn, TOut> OneWay<TIn, TOut>(
IObservable<TIn> root,
Expression<Func<TIn, TOut>> read,
Optional<TOut> fallbackValue = default)
where TIn : class
{
return new TypedBindingExpression<TIn, TOut>(
root,
read.Compile(),
null,
ExpressionChainVisitor<TIn>.Build(read),
fallbackValue);
}

public static TypedBindingExpression<TIn, TConverted> OneWay<TIn, TOut, TConverted>(
TIn root,
Expression<Func<TIn, TOut>> read,
Func<TOut, TConverted> convert,
Optional<TConverted> fallbackValue = default)
where TIn : class
{
var compiledRead = read.Compile();

return new TypedBindingExpression<TIn, TConverted>(
new Single<TIn>(root),
x => convert(compiledRead(x)),
null,
ExpressionChainVisitor<TIn>.Build(read),
fallbackValue);
}

public static TypedBindingExpression<TIn, TConverted> OneWay<TIn, TOut, TConverted>(
IObservable<TIn> root,
Expression<Func<TIn, TOut>> read,
Func<TOut, TConverted> convert,
Optional<TConverted> fallbackValue = default)
where TIn : class
{
var compiledRead = read.Compile();

return new TypedBindingExpression<TIn, TConverted>(
root,
x => convert(compiledRead(x)),
null,
ExpressionChainVisitor<TIn>.Build(read),
fallbackValue);
}

public static TypedBindingExpression<TIn, TOut> TwoWay<TIn, TOut>(
TIn root,
Expression<Func<TIn, TOut>> read,
Action<TIn, TOut> write,
Optional<TOut> fallbackValue = default)
where TIn : class
{
return new TypedBindingExpression<TIn, TOut>(
new Single<TIn>(root),
read.Compile(),
write,
ExpressionChainVisitor<TIn>.Build(read),
fallbackValue);
}

public static TypedBindingExpression<TIn, TOut> TwoWay<TIn, TOut>(
IObservable<TIn> root,
Expression<Func<TIn, TOut>> read,
Action<TIn, TOut> write,
Optional<TOut> fallbackValue = default)
where TIn : class
{
return new TypedBindingExpression<TIn, TOut>(
root,
read.Compile(),
write,
ExpressionChainVisitor<TIn>.Build(read),
fallbackValue);
}

public static TypedBindingExpression<TIn, TConverted> TwoWay<TIn, TOut, TConverted>(
TIn root,
Expression<Func<TIn, TOut>> read,
Action<TIn, TOut> write,
Func<TOut, TConverted> convert,
Func<TConverted, TOut> convertBack,
Optional<TConverted> fallbackValue = default)
where TIn : class
{
var compiledRead = read.Compile();

return new TypedBindingExpression<TIn, TConverted>(
new Single<TIn>(root),
x => convert(compiledRead(x)),
(o, v) => write(o, convertBack(v)),
ExpressionChainVisitor<TIn>.Build(read),
fallbackValue);
}

public static TypedBindingExpression<TIn, TConverted> TwoWay<TIn, TOut, TConverted>(
IObservable<TIn> root,
Expression<Func<TIn, TOut>> read,
Action<TIn, TOut> write,
Func<TOut, TConverted> convert,
Func<TConverted, TOut> convertBack,
Optional<TConverted> fallbackValue = default)
where TIn : class
{
var compiledRead = read.Compile();

return new TypedBindingExpression<TIn, TConverted>(
root,
x => convert(compiledRead(x)),
(o, v) => write(o, convertBack(v)),
ExpressionChainVisitor<TIn>.Build(read),
fallbackValue);
}

private class Single<T> : IObservable<T?>, IDisposable where T : class
{
private WeakReference<T> _value;

public Single(T value) => _value = new WeakReference<T>(value);

public IDisposable Subscribe(IObserver<T?> observer)
{
if (_value.TryGetTarget(out var value))
{
observer.OnNext(value);
}
else
{
observer.OnNext(default);
}

return this;
}

public void Dispose() { }
}
}
}
Loading

0 comments on commit 70d64e6

Please sign in to comment.