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; } }