csharp/71/Ryder/Ryder/Redirection.Reactive.cs

Redirection.Reactive.cs
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace Ryder
{
    partial clast Redirection
    {
        #region Public methods
        /// 
        ///   Returns an observable that allows observing the specified ,
        ///   and hooking its calls, optionally modifying its return type.
        /// 
        public static ObservableRedirection Observe(MethodBase method)
        {
            if (method == null)
                throw new ArgumentNullException(nameof(method));

            MethodRedirection redirection = CreateDynamicRedirection(method, out int id);

            return new ObservableRedirection(id, redirection);
        }

        /// 
        ///   Observes the specified , hooking its calls and optionally
        ///   modifying its return type.
        /// 
        /// An  that can be disposed to disable the hook.
        public static IDisposable Observe(MethodBase method, Action action, Action onError = null)
        {
            if (method == null)
                throw new ArgumentNullException(nameof(method));
            if (action == null)
                throw new ArgumentNullException(nameof(action));

            return Observe(method).Subscribe(new RedirectionObserver(action, onError));
        }
        #endregion

        /// 
        /// 
        ///   Dictionary that contains all active reactive s.
        /// 
        /// 
        ///   This dictionary is used by the generated methods.
        /// 
        /// 
        internal static readonly Dictionary ObservingRedirections = new Dictionary();

        #region Private utils
        /// 
        ///    that generates IDs for .
        /// 
        private static readonly Random ObservingRedirectionsIdGenerator = new Random();

        /// 
        ///   Method invoked by hooked methods when they are themselves invoked.
        /// 
        // ReSharper disable once SuggestBaseTypeForParameter
        private static object OnInvoked(object sender, object[] arguments, int key)
        {
            var reactiveRedirection = ObservingRedirections[key];
            RedirectionContext value = new RedirectionContext(sender, arguments, reactiveRedirection.UnderlyingRedirection);

            foreach (var observer in reactiveRedirection.Observers)
            {
                observer.OnNext(value);
            }

            return value.GetCustomReturnValueOrOriginal();
        }

        /// 
        ///   Creates a 
        /// 
        private static MethodRedirection CreateDynamicRedirection(MethodBase method, out int id)
        {
            // Make id
            do
            {
                id = ObservingRedirectionsIdGenerator.Next();
            }
            while (ObservingRedirections.ContainsKey(id));

            // Creates an array containing all parameter types
            int diff = method.IsStatic ? 0 : 1;

            ParameterInfo[] originalParameters = method.GetParameters();
            Type[] originalParameterTypes = new Type[originalParameters.Length + diff];

            if (diff == 1 /* !method.IsStatic */)
                originalParameterTypes[0] = method.DeclaringType;

            for (int i = 0; i < originalParameters.Length; i++)
            {
                originalParameterTypes[i + diff] = originalParameters[i].ParameterType;
            }

            // Create an identical method
            bool isCtor = method is ConstructorInfo;
            Type returnType = isCtor ? typeof(void) : ((MethodInfo)method).ReturnType;

            DynamicMethod dyn = new DynamicMethod(
                name:              method.Name,
                attributes:        MethodAttributes.Public | MethodAttributes.Static,
                callingConvention: CallingConventions.Standard,
                returnType:        returnType,
                parameterTypes:    originalParameterTypes,
                owner:             method.DeclaringType,
                skipVisibility:    true);

            // Make the method call the observable
            ILGenerator il = dyn.GetILGenerator();
            {
                // This is in a block to make every more readable,
                // the following comments describe what's happening in the generated method.

                // Emit "this", or "null"
                if (method.IsStatic)
                {
                    il.Emit(OpCodes.Ldnull);
                }
                else
                {
                    il.Emit(OpCodes.Ldarg_0);

                    if (method.DeclaringType.GetTypeInfo().IsValueType)
                    {
                        il.Emit(OpCodes.Ldobj, method.DeclaringType);
                        il.Emit(OpCodes.Box, method.DeclaringType);
                    }
                }

                // Create an array containing all parameters
                il.Emit(OpCodes.Ldc_I4, originalParameters.Length);
                il.Emit(OpCodes.Newarr, typeof(object));

                for (int i = 0; i < originalParameters.Length; i++)
                {
                    il.Emit(OpCodes.Dup);
                    il.Emit(OpCodes.Ldc_I4, i);
                    il.Emit(OpCodes.Ldarg, i + diff);

                    Type parameterType = originalParameterTypes[i + diff];

                    if (parameterType.GetTypeInfo().IsValueType)
                        il.Emit(OpCodes.Box, parameterType);

                    il.Emit(OpCodes.Stelem_Ref);
                }

                // Array is still on stack (thanks to dup)
                // Emit id
                il.Emit(OpCodes.Ldc_I4, id);

                // Call "hook" method
                il.Emit(OpCodes.Call, typeof(Redirection).GetMethod(nameof(OnInvoked), BindingFlags.Static | BindingFlags.NonPublic));

                // Return returned result
                // (But first, cast it if needed)
                if (returnType == typeof(void))
                    il.Emit(OpCodes.Pop);
                else if (returnType.GetTypeInfo().IsValueType)
                    il.Emit(OpCodes.Unbox_Any, returnType);
                else if (returnType != typeof(object))
                    il.Emit(OpCodes.Castclast, returnType);

                il.Emit(OpCodes.Ret);
            }

            // Return the redirection
            return new MethodRedirection(method, dyn, false);
        }
        #endregion
    }
}