Browse Source
`DataAnnotationsValidationPlugin` is public and so it can't be moved. No point in moving the others if this one will be in the wrong place.pull/13970/head
18 changed files with 228 additions and 233 deletions
@ -1,27 +1,91 @@ |
|||||
using System; |
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
using System.Linq; |
using System.Linq; |
||||
|
using System.Reflection; |
||||
using Avalonia.Reactive; |
using Avalonia.Reactive; |
||||
|
|
||||
namespace Avalonia.Data.Core.Plugins; |
namespace Avalonia.Data.Core.Plugins |
||||
|
|
||||
internal class ObservableStreamPlugin<T> : IStreamPlugin |
|
||||
{ |
{ |
||||
public bool Match(WeakReference<object?> reference) |
/// <summary>
|
||||
|
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
|
||||
|
/// </summary>
|
||||
|
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] |
||||
|
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
||||
|
internal class ObservableStreamPlugin : IStreamPlugin |
||||
{ |
{ |
||||
return reference.TryGetTarget(out var target) && target is IObservable<T>; |
private static MethodInfo? s_observableGeneric; |
||||
} |
private static MethodInfo? s_observableSelect; |
||||
|
|
||||
public IObservable<object?> Start(WeakReference<object?> reference) |
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] |
||||
{ |
public ObservableStreamPlugin() |
||||
if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs)) |
|
||||
{ |
{ |
||||
return Observable.Empty<object?>(); |
|
||||
} |
} |
||||
else if (target is IObservable<object?> obj) |
|
||||
|
/// <summary>
|
||||
|
/// Checks whether this plugin handles the specified value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="reference">A weak reference to the value.</param>
|
||||
|
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
|
||||
|
public virtual bool Match(WeakReference<object?> reference) |
||||
{ |
{ |
||||
return obj; |
reference.TryGetTarget(out var target); |
||||
|
|
||||
|
return target != null && target.GetType().GetInterfaces().Any(x => |
||||
|
x.IsGenericType && |
||||
|
x.GetGenericTypeDefinition() == typeof(IObservable<>)); |
||||
} |
} |
||||
|
|
||||
return obs.Select(x => (object?)x); |
/// <summary>
|
||||
|
/// Starts producing output based on the specified value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="reference">A weak reference to the object.</param>
|
||||
|
/// <returns>
|
||||
|
/// An observable that produces the output for the value.
|
||||
|
/// </returns>
|
||||
|
public virtual IObservable<object?> Start(WeakReference<object?> reference) |
||||
|
{ |
||||
|
if (!reference.TryGetTarget(out var target) || target is null) |
||||
|
return Observable.Empty<object?>(); |
||||
|
|
||||
|
// If the observable returns a reference type then we can cast it.
|
||||
|
if (target is IObservable<object?> result) |
||||
|
{ |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
// If the observable returns a value type then we need to call Observable.Select on it.
|
||||
|
// First get the type of T in `IObservable<T>`.
|
||||
|
var sourceType = target.GetType().GetInterfaces().First(x => |
||||
|
x.IsGenericType && |
||||
|
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0]; |
||||
|
|
||||
|
// Get the BoxObservable<T> method.
|
||||
|
var select = GetBoxObservable(sourceType); |
||||
|
|
||||
|
// Call BoxObservable(target);
|
||||
|
return (IObservable<object?>)select.Invoke( |
||||
|
null, |
||||
|
new[] { target })!; |
||||
|
} |
||||
|
|
||||
|
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
||||
|
private static MethodInfo GetBoxObservable(Type source) |
||||
|
{ |
||||
|
return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source); |
||||
|
} |
||||
|
|
||||
|
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
||||
|
private static MethodInfo GetBoxObservable() |
||||
|
{ |
||||
|
return s_observableSelect |
||||
|
??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic) |
||||
|
?? throw new InvalidOperationException("BoxObservable method was not found."); |
||||
|
} |
||||
|
|
||||
|
private static IObservable<object?> BoxObservable<T>(IObservable<T> source) |
||||
|
{ |
||||
|
return source.Select(v => (object?)v); |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Reactive; |
||||
|
|
||||
|
namespace Avalonia.Data.Core.Plugins; |
||||
|
|
||||
|
internal class ObservableStreamPlugin<T> : IStreamPlugin |
||||
|
{ |
||||
|
public bool Match(WeakReference<object?> reference) |
||||
|
{ |
||||
|
return reference.TryGetTarget(out var target) && target is IObservable<T>; |
||||
|
} |
||||
|
|
||||
|
public IObservable<object?> Start(WeakReference<object?> reference) |
||||
|
{ |
||||
|
if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs)) |
||||
|
{ |
||||
|
return Observable.Empty<object?>(); |
||||
|
} |
||||
|
else if (target is IObservable<object?> obj) |
||||
|
{ |
||||
|
return obj; |
||||
|
} |
||||
|
|
||||
|
return obs.Select(x => (object?)x); |
||||
|
} |
||||
|
} |
||||
@ -1,91 +0,0 @@ |
|||||
using System; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using System.Linq; |
|
||||
using System.Reflection; |
|
||||
using Avalonia.Reactive; |
|
||||
|
|
||||
namespace Avalonia.Data.Core.Plugins.Reflection |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
|
|
||||
/// </summary>
|
|
||||
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] |
|
||||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|
||||
internal class ObservableStreamPlugin : IStreamPlugin |
|
||||
{ |
|
||||
private static MethodInfo? s_observableGeneric; |
|
||||
private static MethodInfo? s_observableSelect; |
|
||||
|
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] |
|
||||
public ObservableStreamPlugin() |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Checks whether this plugin handles the specified value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="reference">A weak reference to the value.</param>
|
|
||||
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
|
|
||||
public virtual bool Match(WeakReference<object?> reference) |
|
||||
{ |
|
||||
reference.TryGetTarget(out var target); |
|
||||
|
|
||||
return target != null && target.GetType().GetInterfaces().Any(x => |
|
||||
x.IsGenericType && |
|
||||
x.GetGenericTypeDefinition() == typeof(IObservable<>)); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Starts producing output based on the specified value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="reference">A weak reference to the object.</param>
|
|
||||
/// <returns>
|
|
||||
/// An observable that produces the output for the value.
|
|
||||
/// </returns>
|
|
||||
public virtual IObservable<object?> Start(WeakReference<object?> reference) |
|
||||
{ |
|
||||
if (!reference.TryGetTarget(out var target) || target is null) |
|
||||
return Observable.Empty<object?>(); |
|
||||
|
|
||||
// If the observable returns a reference type then we can cast it.
|
|
||||
if (target is IObservable<object?> result) |
|
||||
{ |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
// If the observable returns a value type then we need to call Observable.Select on it.
|
|
||||
// First get the type of T in `IObservable<T>`.
|
|
||||
var sourceType = target.GetType().GetInterfaces().First(x => |
|
||||
x.IsGenericType && |
|
||||
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0]; |
|
||||
|
|
||||
// Get the BoxObservable<T> method.
|
|
||||
var select = GetBoxObservable(sourceType); |
|
||||
|
|
||||
// Call BoxObservable(target);
|
|
||||
return (IObservable<object?>)select.Invoke( |
|
||||
null, |
|
||||
new[] { target })!; |
|
||||
} |
|
||||
|
|
||||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|
||||
private static MethodInfo GetBoxObservable(Type source) |
|
||||
{ |
|
||||
return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source); |
|
||||
} |
|
||||
|
|
||||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|
||||
private static MethodInfo GetBoxObservable() |
|
||||
{ |
|
||||
return s_observableSelect |
|
||||
??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic) |
|
||||
?? throw new InvalidOperationException("BoxObservable method was not found."); |
|
||||
} |
|
||||
|
|
||||
private static IObservable<object?> BoxObservable<T>(IObservable<T> source) |
|
||||
{ |
|
||||
return source.Select(v => (object?)v); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,85 +0,0 @@ |
|||||
using System; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using System.Reflection; |
|
||||
using System.Threading.Tasks; |
|
||||
using Avalonia.Reactive; |
|
||||
|
|
||||
namespace Avalonia.Data.Core.Plugins.Reflection |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.
|
|
||||
/// </summary>
|
|
||||
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] |
|
||||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|
||||
internal class TaskStreamPlugin : IStreamPlugin |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Checks whether this plugin handles the specified value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="reference">A weak reference to the value.</param>
|
|
||||
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
|
|
||||
public virtual bool Match(WeakReference<object?> reference) |
|
||||
{ |
|
||||
reference.TryGetTarget(out var target); |
|
||||
|
|
||||
return target is Task; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Starts producing output based on the specified value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="reference">A weak reference to the object.</param>
|
|
||||
/// <returns>
|
|
||||
/// An observable that produces the output for the value.
|
|
||||
/// </returns>
|
|
||||
public virtual IObservable<object?> Start(WeakReference<object?> reference) |
|
||||
{ |
|
||||
reference.TryGetTarget(out var target); |
|
||||
|
|
||||
if (target is Task task) |
|
||||
{ |
|
||||
var resultProperty = task.GetType().GetRuntimeProperty("Result"); |
|
||||
|
|
||||
if (resultProperty != null) |
|
||||
{ |
|
||||
switch (task.Status) |
|
||||
{ |
|
||||
case TaskStatus.RanToCompletion: |
|
||||
case TaskStatus.Faulted: |
|
||||
return HandleCompleted(task); |
|
||||
default: |
|
||||
var subject = new LightweightSubject<object?>(); |
|
||||
task.ContinueWith( |
|
||||
x => HandleCompleted(task).Subscribe(subject), |
|
||||
TaskScheduler.FromCurrentSynchronizationContext()) |
|
||||
.ConfigureAwait(false); |
|
||||
return subject; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return Observable.Empty<object?>(); |
|
||||
} |
|
||||
|
|
||||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|
||||
private static IObservable<object?> HandleCompleted(Task task) |
|
||||
{ |
|
||||
var resultProperty = task.GetType().GetRuntimeProperty("Result"); |
|
||||
|
|
||||
if (resultProperty != null) |
|
||||
{ |
|
||||
switch (task.Status) |
|
||||
{ |
|
||||
case TaskStatus.RanToCompletion: |
|
||||
return Observable.Return(resultProperty.GetValue(task)); |
|
||||
case TaskStatus.Faulted: |
|
||||
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error)); |
|
||||
default: |
|
||||
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return Observable.Empty<object>(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,49 +1,85 @@ |
|||||
using System; |
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Reflection; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Avalonia.Reactive; |
using Avalonia.Reactive; |
||||
|
|
||||
namespace Avalonia.Data.Core.Plugins; |
namespace Avalonia.Data.Core.Plugins |
||||
|
|
||||
internal class TaskStreamPlugin<T> : IStreamPlugin |
|
||||
{ |
{ |
||||
public bool Match(WeakReference<object?> reference) |
/// <summary>
|
||||
|
/// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.
|
||||
|
/// </summary>
|
||||
|
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] |
||||
|
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
||||
|
internal class TaskStreamPlugin : IStreamPlugin |
||||
{ |
{ |
||||
return reference.TryGetTarget(out var target) && target is Task<T>; |
/// <summary>
|
||||
} |
/// Checks whether this plugin handles the specified value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="reference">A weak reference to the value.</param>
|
||||
|
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
|
||||
|
public virtual bool Match(WeakReference<object?> reference) |
||||
|
{ |
||||
|
reference.TryGetTarget(out var target); |
||||
|
|
||||
public IObservable<object?> Start(WeakReference<object?> reference) |
return target is Task; |
||||
{ |
} |
||||
if (!(reference.TryGetTarget(out var target) && target is Task<T> task)) |
|
||||
|
/// <summary>
|
||||
|
/// Starts producing output based on the specified value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="reference">A weak reference to the object.</param>
|
||||
|
/// <returns>
|
||||
|
/// An observable that produces the output for the value.
|
||||
|
/// </returns>
|
||||
|
public virtual IObservable<object?> Start(WeakReference<object?> reference) |
||||
{ |
{ |
||||
|
reference.TryGetTarget(out var target); |
||||
|
|
||||
|
if (target is Task task) |
||||
|
{ |
||||
|
var resultProperty = task.GetType().GetRuntimeProperty("Result"); |
||||
|
|
||||
|
if (resultProperty != null) |
||||
|
{ |
||||
|
switch (task.Status) |
||||
|
{ |
||||
|
case TaskStatus.RanToCompletion: |
||||
|
case TaskStatus.Faulted: |
||||
|
return HandleCompleted(task); |
||||
|
default: |
||||
|
var subject = new LightweightSubject<object?>(); |
||||
|
task.ContinueWith( |
||||
|
x => HandleCompleted(task).Subscribe(subject), |
||||
|
TaskScheduler.FromCurrentSynchronizationContext()) |
||||
|
.ConfigureAwait(false); |
||||
|
return subject; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
return Observable.Empty<object?>(); |
return Observable.Empty<object?>(); |
||||
} |
} |
||||
|
|
||||
switch (task.Status) |
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
||||
|
private static IObservable<object?> HandleCompleted(Task task) |
||||
{ |
{ |
||||
case TaskStatus.RanToCompletion: |
var resultProperty = task.GetType().GetRuntimeProperty("Result"); |
||||
case TaskStatus.Faulted: |
|
||||
return HandleCompleted(task); |
|
||||
default: |
|
||||
var subject = new LightweightSubject<object?>(); |
|
||||
task.ContinueWith( |
|
||||
_ => HandleCompleted(task).Subscribe(subject), |
|
||||
TaskScheduler.FromCurrentSynchronizationContext()) |
|
||||
.ConfigureAwait(false); |
|
||||
return subject; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
if (resultProperty != null) |
||||
|
{ |
||||
|
switch (task.Status) |
||||
|
{ |
||||
|
case TaskStatus.RanToCompletion: |
||||
|
return Observable.Return(resultProperty.GetValue(task)); |
||||
|
case TaskStatus.Faulted: |
||||
|
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error)); |
||||
|
default: |
||||
|
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
private static IObservable<object?> HandleCompleted(Task<T> task) |
return Observable.Empty<object>(); |
||||
{ |
|
||||
switch (task.Status) |
|
||||
{ |
|
||||
case TaskStatus.RanToCompletion: |
|
||||
return Observable.Return((object?)task.Result); |
|
||||
case TaskStatus.Faulted: |
|
||||
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error)); |
|
||||
default: |
|
||||
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,49 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Reactive; |
||||
|
|
||||
|
namespace Avalonia.Data.Core.Plugins; |
||||
|
|
||||
|
internal class TaskStreamPlugin<T> : IStreamPlugin |
||||
|
{ |
||||
|
public bool Match(WeakReference<object?> reference) |
||||
|
{ |
||||
|
return reference.TryGetTarget(out var target) && target is Task<T>; |
||||
|
} |
||||
|
|
||||
|
public IObservable<object?> Start(WeakReference<object?> reference) |
||||
|
{ |
||||
|
if (!(reference.TryGetTarget(out var target) && target is Task<T> task)) |
||||
|
{ |
||||
|
return Observable.Empty<object?>(); |
||||
|
} |
||||
|
|
||||
|
switch (task.Status) |
||||
|
{ |
||||
|
case TaskStatus.RanToCompletion: |
||||
|
case TaskStatus.Faulted: |
||||
|
return HandleCompleted(task); |
||||
|
default: |
||||
|
var subject = new LightweightSubject<object?>(); |
||||
|
task.ContinueWith( |
||||
|
_ => HandleCompleted(task).Subscribe(subject), |
||||
|
TaskScheduler.FromCurrentSynchronizationContext()) |
||||
|
.ConfigureAwait(false); |
||||
|
return subject; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static IObservable<object?> HandleCompleted(Task<T> task) |
||||
|
{ |
||||
|
switch (task.Status) |
||||
|
{ |
||||
|
case TaskStatus.RanToCompletion: |
||||
|
return Observable.Return((object?)task.Result); |
||||
|
case TaskStatus.Faulted: |
||||
|
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error)); |
||||
|
default: |
||||
|
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue