using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
namespace Avalonia.Data.Core.Plugins
{
///
/// Handles binding to s for the '^' stream binding operator.
///
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
public class ObservableStreamPlugin : IStreamPlugin
{
static MethodInfo? observableSelect;
///
/// Checks whether this plugin handles the specified value.
///
/// A weak reference to the value.
/// True if the plugin can handle the value; otherwise false.
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public virtual bool Match(WeakReference reference)
{
reference.TryGetTarget(out var target);
return target != null && target.GetType().GetInterfaces().Any(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>));
}
///
/// Starts producing output based on the specified value.
///
/// A weak reference to the object.
///
/// An observable that produces the output for the value.
///
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public virtual IObservable Start(WeakReference reference)
{
if (!reference.TryGetTarget(out var target) || target is null)
return Observable.Empty();
// If the observable returns a reference type then we can cast it.
if (target is IObservable 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`.
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)select.Invoke(
null,
new object[] { target, box })!;
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
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 value) => (object?)value;
}
}