-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial reboot of typed bindings feature.
Following on from #5510,
- Loading branch information
Showing
15 changed files
with
3,046 additions
and
0 deletions.
There are no files selected for viewing
81 changes: 81 additions & 0 deletions
81
src/Avalonia.Base/Data/Core/Parsers/ExpressionChainVisitor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { } | ||
} | ||
} | ||
} |
Oops, something went wrong.