Как перехватить асинхронный метод с IAsyncEnumerable <T>в качестве возвращаемого типа в .netcore 3.0 - PullRequest
0 голосов
/ 18 октября 2019

Я пытаюсь реализовать прокси-сервер, который берет на себя ведение журнала вызовов методов. С осознанием асинхронных методов, которые должны быть вызваны, и некоторые журналы должны создаваться только после завершения метода, но прокси-метод не должен быть вызовом blockig.

После многих попыток это решение, которое я придумал.

namespace ClassLibrary1
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Reflection;
    using System.Threading.Tasks;

    public static class ObjectExtender
    {
        internal static bool IsOfGenericType(this object obj, Type check, out Type? genericType)
        {
            Type actType = obj.GetType();
            while (actType != null && actType != typeof(object))
            {
                if (actType.IsGenericType && actType.GetGenericTypeDefinition() == check.GetGenericTypeDefinition())
                {
                    genericType = actType;
                    return true;
                }

                actType = actType.BaseType;
            }

            genericType = null;
            return false;
        }
    }

    public class Class1<T> : DispatchProxy
    {
        private static readonly MethodInfo AsyncEnumeration;
        private static readonly Dictionary<Type, MethodInfo> CachedAsyncEnumerationMethodInfos = new Dictionary<Type, MethodInfo>();
        private static readonly Dictionary<Type, MethodInfo> CachedGenericTaskMethodInfos = new Dictionary<Type, MethodInfo>();
        private static readonly Dictionary<Type, MethodInfo> CachedSyncEnumerationMethodInfos = new Dictionary<Type, MethodInfo>();
        private static readonly MethodInfo GenericTask;
        private static readonly MethodInfo SyncEnumeration;
        private T _decorated = default!;

        static Class1()
        {
            GenericTask = typeof(Class1<T>).GetMethod("HandleTaskGenericAsync", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
            AsyncEnumeration = typeof(Class1<T>).GetMethod("Wrapper", BindingFlags.Static | BindingFlags.NonPublic           | BindingFlags.DeclaredOnly);
            SyncEnumeration = typeof(Class1<T>).GetMethod("SyncWrapper", BindingFlags.Static | BindingFlags.NonPublic        | BindingFlags.DeclaredOnly);
        }

        public static T Create(T decorated)
        {
            T proxy = Create<T, Class1<T>>();
            Class1<T> ap = proxy as Class1<T> ?? throw new ArgumentNullException(nameof(decorated));
            ap._decorated = decorated;

            return proxy;
        }

        private static Task<T2> HandleTaskGenericAsync<T1, T2>(T1 result, MethodInfo methodName) where T1 : Task<T2>
        {
            return result.ContinueWith(parent =>
                                       {
                                           Console.WriteLine($"After: {methodName}");
                                           return parent.Result;
                                       });
        }

        protected override object Invoke(MethodInfo targetMethod, object[] args)
        {
            try
            {
                Console.WriteLine($"Before: {targetMethod}");

                object result = targetMethod.Invoke(_decorated, args);

                if (result is Task resultTask)
                {
                    if (!resultTask.IsOfGenericType(typeof(Task<>), out Type? genericType))
                    {
                        return resultTask.ContinueWith(task =>
                                                       {
                                                           if (task.Exception != null)
                                                           {
                                                               Console.WriteLine($"{task.Exception.InnerException ?? task.Exception}, {targetMethod}");
                                                           }
                                                           else
                                                           {
                                                               Console.WriteLine($"After: {targetMethod}");
                                                           }
                                                       });
                    }

                    Debug.Assert(genericType != null, nameof(genericType) + " != null");
                    Type resultType = genericType.GetGenericArguments()[0]; // Task<> hat nur einen.
                    if (!CachedGenericTaskMethodInfos.ContainsKey(resultType))
                    {
                        CachedGenericTaskMethodInfos.Add(resultType, GenericTask.MakeGenericMethod(genericType, resultType));
                    }

                    return CachedGenericTaskMethodInfos[resultType].Invoke(null, new object[] {resultTask, targetMethod});
                }

                Type returnType = targetMethod.ReturnType;
                if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>))
                {
                    Type resultType = returnType.GetGenericArguments()[0]; //IAsyncEnumerable hat nur eines
                    if (!CachedAsyncEnumerationMethodInfos.ContainsKey(resultType))
                    {
                        CachedAsyncEnumerationMethodInfos.Add(resultType, AsyncEnumeration.MakeGenericMethod(resultType));
                    }

                    return CachedAsyncEnumerationMethodInfos[resultType].Invoke(null, new[] {result, targetMethod});
                }

                if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                {
                    Type resultType = returnType.GetGenericArguments()[0]; //IAsyncEnumerable hat nur eines
                    if (!CachedSyncEnumerationMethodInfos.ContainsKey(resultType))
                    {
                        CachedSyncEnumerationMethodInfos.Add(resultType, SyncEnumeration.MakeGenericMethod(resultType));
                    }

                    return CachedSyncEnumerationMethodInfos[resultType].Invoke(null, new[] {result, targetMethod});
                }

                Console.WriteLine($"After: {targetMethod}");

                return result;
            }
            catch (TargetInvocationException ex)
            {
                Console.WriteLine($"{ex.InnerException ?? ex}, {targetMethod}");
                throw;
            }
        }

        private static IEnumerable<T> SyncWrapper<T>(IEnumerable<T> inner, MethodInfo targetMethod)
        {
            foreach (T t in inner)
            {
                yield return t;
            }

            Console.WriteLine($"After List: {targetMethod}");
        }

        private static async IAsyncEnumerable<T> Wrapper<T>(IAsyncEnumerable<T> inner, MethodInfo targetMethod)
        {
            await foreach (T t in inner)
            {
                yield return t;
            }

            Console.WriteLine($"After List: {targetMethod}");
        }
    }
}

Этот прокси перехватывает вызовы методов так, как я хотел. Это результат моего теста

---Test sync calls---
Before: Void Run()
Inside: Run()
After: Void Run()
Before: System.Collections.Generic.IEnumerable`1[System.Int32] RunEnumerator[Int32](Int32[])
Inside Start: RunEnumerator()
Erg: 1
Erg: 2
Erg: 3
Erg: 4
Inside Ende: RunEnumerator()
After List: System.Collections.Generic.IEnumerable`1[System.Int32] RunEnumerator[Int32](Int32[])
---Test async calls---
Before: System.Threading.Tasks.Task RunAsync()
Inside: RunAsync()
After: System.Threading.Tasks.Task RunAsync()
Before: System.Threading.Tasks.Task RunAwaitAsync()
Inside: RunAwaitAsync()
After: System.Threading.Tasks.Task RunAwaitAsync()
Before: System.Threading.Tasks.Task`1[System.String] RunAwaitGenericTask[String](System.String)
Inside: RunAwaitGenericTask()
After: System.Threading.Tasks.Task`1[System.String] RunAwaitGenericTask[String](System.String)
Before: System.Collections.Generic.IAsyncEnumerable`1[System.Int32] RunAwaitGenericEnumeratorTask[Int32](Int32[])
Inside Start: RunAwaitGenericEnumeratorTask()
Erg: 1
Erg: 2
Erg: 3
Erg: 4
Inside Ende: RunAwaitGenericEnumeratorTask()
After List: System.Collections.Generic.IAsyncEnumerable`1[System.Int32] RunAwaitGenericEnumeratorTask[Int32](Int32[])

1 Ответ

0 голосов
/ 22 октября 2019

Наконец я нашел решение. Спасибо Jeroen Mostert за то, что он указал мне правильное направление.

namespace ClassLibrary1
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Reflection;
    using System.Threading.Tasks;

    public static class ObjectExtender
    {
        internal static bool IsOfGenericType(this object obj, Type check, out Type? genericType)
        {
            Type actType = obj.GetType();
            while (actType != null && actType != typeof(object))
            {
                if (actType.IsGenericType && actType.GetGenericTypeDefinition() == check.GetGenericTypeDefinition())
                {
                    genericType = actType;
                    return true;
                }

                actType = actType.BaseType;
            }

            genericType = null;
            return false;
        }
    }

    public class Class1<T> : DispatchProxy
    {
        private static readonly MethodInfo AsyncEnumeration;
        private static readonly Dictionary<Type, MethodInfo> CachedAsyncEnumerationMethodInfos = new Dictionary<Type, MethodInfo>();
        private static readonly Dictionary<Type, MethodInfo> CachedGenericTaskMethodInfos = new Dictionary<Type, MethodInfo>();
        private static readonly Dictionary<Type, MethodInfo> CachedSyncEnumerationMethodInfos = new Dictionary<Type, MethodInfo>();
        private static readonly MethodInfo GenericTask;
        private static readonly MethodInfo SyncEnumeration;
        private T _decorated = default!;

        static Class1()
        {
            GenericTask = typeof(Class1<T>).GetMethod("HandleTaskGenericAsync", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
            AsyncEnumeration = typeof(Class1<T>).GetMethod("Wrapper", BindingFlags.Static | BindingFlags.NonPublic           | BindingFlags.DeclaredOnly);
            SyncEnumeration = typeof(Class1<T>).GetMethod("SyncWrapper", BindingFlags.Static | BindingFlags.NonPublic        | BindingFlags.DeclaredOnly);
        }

        public static T Create(T decorated)
        {
            T proxy = Create<T, Class1<T>>();
            Class1<T> ap = proxy as Class1<T> ?? throw new ArgumentNullException(nameof(decorated));
            ap._decorated = decorated;

            return proxy;
        }

        private static Task<T2> HandleTaskGenericAsync<T1, T2>(T1 result, MethodInfo methodName) where T1 : Task<T2>
        {
            return result.ContinueWith(parent =>
                                       {
                                           Console.WriteLine($"After: {methodName}");
                                           return parent.Result;
                                       });
        }

        protected override object Invoke(MethodInfo targetMethod, object[] args)
        {
            try
            {
                Console.WriteLine($"Before: {targetMethod}");

                object result = targetMethod.Invoke(_decorated, args);

                if (result is Task resultTask)
                {
                    if (!resultTask.IsOfGenericType(typeof(Task<>), out Type? genericType))
                    {
                        return resultTask.ContinueWith(task =>
                                                       {
                                                           if (task.Exception != null)
                                                           {
                                                               Console.WriteLine($"{task.Exception.InnerException ?? task.Exception}, {targetMethod}");
                                                           }
                                                           else
                                                           {
                                                               Console.WriteLine($"After: {targetMethod}");
                                                           }
                                                       });
                    }

                    Debug.Assert(genericType != null, nameof(genericType) + " != null");
                    Type resultType = genericType.GetGenericArguments()[0]; // Task<> hat nur einen.
                    if (!CachedGenericTaskMethodInfos.ContainsKey(resultType))
                    {
                        CachedGenericTaskMethodInfos.Add(resultType, GenericTask.MakeGenericMethod(genericType, resultType));
                    }

                    return CachedGenericTaskMethodInfos[resultType].Invoke(null, new object[] {resultTask, targetMethod});
                }

                Type returnType = targetMethod.ReturnType;
                if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>))
                {
                    Type resultType = returnType.GetGenericArguments()[0]; //IAsyncEnumerable hat nur eines
                    if (!CachedAsyncEnumerationMethodInfos.ContainsKey(resultType))
                    {
                        CachedAsyncEnumerationMethodInfos.Add(resultType, AsyncEnumeration.MakeGenericMethod(resultType));
                    }

                    return CachedAsyncEnumerationMethodInfos[resultType].Invoke(null, new[] {result, targetMethod});
                }

                if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                {
                    Type resultType = returnType.GetGenericArguments()[0]; //IAsyncEnumerable hat nur eines
                    if (!CachedSyncEnumerationMethodInfos.ContainsKey(resultType))
                    {
                        CachedSyncEnumerationMethodInfos.Add(resultType, SyncEnumeration.MakeGenericMethod(resultType));
                    }

                    return CachedSyncEnumerationMethodInfos[resultType].Invoke(null, new[] {result, targetMethod});
                }

                Console.WriteLine($"After: {targetMethod}");

                return result;
            }
            catch (TargetInvocationException ex)
            {
                Console.WriteLine($"{ex.InnerException ?? ex}, {targetMethod}");
                throw;
            }
        }

        private static IEnumerable<T> SyncWrapper<T>(IEnumerable<T> inner, MethodInfo targetMethod)
        {
            foreach (T t in inner)
            {
                yield return t;
            }

            Console.WriteLine($"After List: {targetMethod}");
        }

        private static async IAsyncEnumerable<T> Wrapper<T>(IAsyncEnumerable<T> inner, MethodInfo targetMethod)
        {
            await foreach (T t in inner)
            {
                yield return t;
            }

            Console.WriteLine($"After List: {targetMethod}");
        }
    }
}
...