csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
106 lines
4.0 KiB
106 lines
4.0 KiB
using System;
|
|
using System.Linq;
|
|
using System.Reactive.Linq;
|
|
using System.Reflection;
|
|
|
|
#nullable enable
|
|
|
|
namespace Avalonia.Data.Core.Plugins
|
|
{
|
|
/// <summary>
|
|
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
|
|
/// </summary>
|
|
public class ObservableStreamPlugin : IStreamPlugin
|
|
{
|
|
static MethodInfo? observableSelect;
|
|
|
|
/// <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 Observable.Select method.
|
|
var select = GetObservableSelect(sourceType);
|
|
|
|
// Make a Box<> delegate of the correct type.
|
|
var funcType = typeof(Func<,>).MakeGenericType(sourceType, typeof(object));
|
|
var box = GetType().GetMethod(nameof(Box), BindingFlags.Static | BindingFlags.NonPublic)
|
|
.MakeGenericMethod(sourceType)
|
|
.CreateDelegate(funcType);
|
|
|
|
// Call Observable.Select(target, box);
|
|
return (IObservable<object>)select.Invoke(
|
|
null,
|
|
new object[] { target, box });
|
|
}
|
|
|
|
private static MethodInfo GetObservableSelect(Type source)
|
|
{
|
|
return GetObservableSelect().MakeGenericMethod(source, typeof(object));
|
|
}
|
|
|
|
private static MethodInfo GetObservableSelect()
|
|
{
|
|
if (observableSelect == null)
|
|
{
|
|
observableSelect = typeof(Observable).GetRuntimeMethods().First(x =>
|
|
{
|
|
if (x.Name == nameof(Observable.Select) &&
|
|
x.ContainsGenericParameters &&
|
|
x.GetGenericArguments().Length == 2)
|
|
{
|
|
var parameters = x.GetParameters();
|
|
|
|
if (parameters.Length == 2 &&
|
|
parameters[0].ParameterType.IsConstructedGenericType &&
|
|
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IObservable<>) &&
|
|
parameters[1].ParameterType.IsConstructedGenericType &&
|
|
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
return observableSelect;
|
|
}
|
|
|
|
private static object? Box<T>(T value) => (object?)value;
|
|
}
|
|
}
|
|
|