private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) { if (methodInfo == null) { throw new ArgumentNullException(nameof(methodInfo)); } MethodInfo = methodInfo; MethodParameters = methodInfo.GetParameters(); TargetTypeInfo = targetTypeInfo; MethodReturnType = methodInfo.ReturnType; var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(MethodReturnType, out var coercedAwaitableInfo); IsMethodAsync = isAwaitable; AsyncResultType = isAwaitable ? coercedAwaitableInfo.AwaitableInfo.ResultType : null; // Upstream code may prefer to use the sync-executor even for async methods, because if it knows // that the result is a specific Task<T> where T is known, then it can directly cast to that type // and await it without the extra heap allocations involved in the _executorAsync code path. _executor = GetExecutor(methodInfo, targetTypeInfo); if (IsMethodAsync) { _executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo); } _parameterDefaultValues = parameterDefaultValues; }
public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info) { if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo)) { info = new CoercedAwaitableInfo(directlyAwaitableInfo); return(true); } // It's not directly awaitable, but maybe we can coerce it. // Currently we support coercing FSharpAsync<T>. if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, out var coercerExpression, out var coercerResultType)) { if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) { info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); return(true); } } info = default(CoercedAwaitableInfo); return(false); }
private static MethodExecutorAsync GetExecutorAsync( MethodInfo methodInfo, TypeInfo targetTypeInfo, CoercedAwaitableInfo coercedAwaitableInfo) { // Parameters to executor var targetParameter = Expression.Parameter(typeof(object), "target"); var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); // Build parameter list var parameters = new List <Expression>(); var paramInfos = methodInfo.GetParameters(); for (int i = 0; i < paramInfos.Length; i++) { var paramInfo = paramInfos[i]; var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); // valueCast is "(Ti) parameters[i]" parameters.Add(valueCast); } // Call method MethodCallExpression methodCall; if (targetTypeInfo == null) // Method is static { methodCall = Expression.Call(null, methodInfo, parameters); } else { var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); methodCall = Expression.Call(instanceCast, methodInfo, parameters); } // Using the method return value, construct an ObjectMethodExecutorAwaitable based on // the info we have about its implementation of the awaitable pattern. Note that all // the funcs/actions we construct here are precompiled, so that only one instance of // each is preserved throughout the lifetime of the ObjectMethodExecutor. // var getAwaiterFunc = (object awaitable) => // (object)((CustomAwaitableType)awaitable).GetAwaiter(); var customAwaitableParam = Expression.Parameter(typeof(object), "awaitable"); var awaitableInfo = coercedAwaitableInfo.AwaitableInfo; var postCoercionMethodReturnType = coercedAwaitableInfo.CoercerResultType ?? methodInfo.ReturnType; var getAwaiterFunc = Expression.Lambda <Func <object, object> >( Expression.Convert( Expression.Call( Expression.Convert(customAwaitableParam, postCoercionMethodReturnType), awaitableInfo.GetAwaiterMethod), typeof(object)), customAwaitableParam).Compile(); // var isCompletedFunc = (object awaiter) => // ((CustomAwaiterType)awaiter).IsCompleted; var isCompletedParam = Expression.Parameter(typeof(object), "awaiter"); var isCompletedFunc = Expression.Lambda <Func <object, bool> >( Expression.MakeMemberAccess( Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType), awaitableInfo.AwaiterIsCompletedProperty), isCompletedParam).Compile(); var getResultParam = Expression.Parameter(typeof(object), "awaiter"); Func <object, object> getResultFunc; if (awaitableInfo.ResultType == typeof(void)) { // var getResultFunc = (object awaiter) => // { // ((CustomAwaiterType)awaiter).GetResult(); // We need to invoke this to surface any exceptions // return (object)null; // }; getResultFunc = Expression.Lambda <Func <object, object> >( Expression.Block( Expression.Call( Expression.Convert(getResultParam, awaitableInfo.AwaiterType), awaitableInfo.AwaiterGetResultMethod), Expression.Constant(null) ), getResultParam).Compile(); } else { // var getResultFunc = (object awaiter) => // (object)((CustomAwaiterType)awaiter).GetResult(); getResultFunc = Expression.Lambda <Func <object, object> >( Expression.Convert( Expression.Call( Expression.Convert(getResultParam, awaitableInfo.AwaiterType), awaitableInfo.AwaiterGetResultMethod), typeof(object)), getResultParam).Compile(); } // var onCompletedFunc = (object awaiter, Action continuation) => { // ((CustomAwaiterType)awaiter).OnCompleted(continuation); // }; var onCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); var onCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); var onCompletedFunc = Expression.Lambda <Action <object, Action> >( Expression.Call( Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType), awaitableInfo.AwaiterOnCompletedMethod, onCompletedParam2), onCompletedParam1, onCompletedParam2).Compile(); Action <object, Action> unsafeOnCompletedFunc = null; if (awaitableInfo.AwaiterUnsafeOnCompletedMethod != null) { // var unsafeOnCompletedFunc = (object awaiter, Action continuation) => { // ((CustomAwaiterType)awaiter).UnsafeOnCompleted(continuation); // }; var unsafeOnCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); var unsafeOnCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); unsafeOnCompletedFunc = Expression.Lambda <Action <object, Action> >( Expression.Call( Expression.Convert(unsafeOnCompletedParam1, awaitableInfo.AwaiterType), awaitableInfo.AwaiterUnsafeOnCompletedMethod, unsafeOnCompletedParam2), unsafeOnCompletedParam1, unsafeOnCompletedParam2).Compile(); } // If we need to pass the method call result through a coercer function to get an // awaitable, then do so. var coercedMethodCall = coercedAwaitableInfo.RequiresCoercion ? Expression.Invoke(coercedAwaitableInfo.CoercerExpression, methodCall) : (Expression)methodCall; // return new ObjectMethodExecutorAwaitable( // (object)coercedMethodCall, // getAwaiterFunc, // isCompletedFunc, // getResultFunc, // onCompletedFunc, // unsafeOnCompletedFunc); var returnValueExpression = Expression.New( _objectMethodExecutorAwaitableConstructor, Expression.Convert(coercedMethodCall, typeof(object)), Expression.Constant(getAwaiterFunc), Expression.Constant(isCompletedFunc), Expression.Constant(getResultFunc), Expression.Constant(onCompletedFunc), Expression.Constant(unsafeOnCompletedFunc, typeof(Action <object, Action>))); var lambda = Expression.Lambda <MethodExecutorAsync>(returnValueExpression, targetParameter, parametersParameter); return(lambda.Compile()); }