// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; namespace Microsoft.Extensions.Internal { internal class ObjectMethodExecutor { private readonly object[] _parameterDefaultValues; private readonly MethodExecutorAsync _executorAsync; private readonly MethodExecutor _executor; private static readonly ConstructorInfo _objectMethodExecutorAwaitableConstructor = typeof(ObjectMethodExecutorAwaitable).GetConstructor(new[] { typeof(object), // customAwaitable typeof(Func), // getAwaiterMethod typeof(Func), // isCompletedMethod typeof(Func), // getResultMethod typeof(Action), // onCompletedMethod typeof(Action) // unsafeOnCompletedMethod }); 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 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; } private delegate ObjectMethodExecutorAwaitable MethodExecutorAsync(object target, object[] parameters); private delegate object MethodExecutor(object target, object[] parameters); private delegate void VoidMethodExecutor(object target, object[] parameters); public MethodInfo MethodInfo { get; } public ParameterInfo[] MethodParameters { get; } public TypeInfo TargetTypeInfo { get; } public Type AsyncResultType { get; } // This field is made internal set because it is set in unit tests. public Type MethodReturnType { get; internal set; } public bool IsMethodAsync { get; } public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) { return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null); } public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) { if (parameterDefaultValues == null) { throw new ArgumentNullException(nameof(parameterDefaultValues)); } return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues); } /// /// Executes the configured method on . This can be used whether or not /// the configured method is asynchronous. /// /// /// Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than /// ExecuteAsync if you know at compile time what the return type is, because then you can directly /// "await" that value (via a cast), and then the generated code will be able to reference the /// resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated /// code will have to treat the resulting awaitable as a boxed object, because it doesn't know at /// compile time what type it would be. /// /// The object whose method is to be executed. /// Parameters to pass to the method. /// The method return value. public object Execute(object target, object[] parameters) { return _executor(target, parameters); } /// /// Executes the configured method on . This can only be used if the configured /// method is asynchronous. /// /// /// If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, /// which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations /// as compared with using Execute and then using "await" on the result value typecasted to the known /// awaitable type. The possible extra heap allocations are for: /// /// 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally /// it's a reference type, and you normally create a new instance per call). /// 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance /// of it, and if it is, it will have to be boxed so the calling code can reference it as an object). /// 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling /// code doesn't know what type it's going to be). /// /// The object whose method is to be executed. /// Parameters to pass to the method. /// An object that you can "await" to get the method return value. public ObjectMethodExecutorAwaitable ExecuteAsync(object target, object[] parameters) { return _executorAsync(target, parameters); } public object GetDefaultValueForParameter(int index) { if (_parameterDefaultValues == null) { throw new InvalidOperationException($"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied."); } if (index < 0 || index > MethodParameters.Length - 1) { throw new ArgumentOutOfRangeException(nameof(index)); } return _parameterDefaultValues[index]; } private static MethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) { // Parameters to executor var targetParameter = Expression.Parameter(typeof(object), "target"); var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); // Build parameter list var parameters = new List(); 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 var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); var methodCall = Expression.Call(instanceCast, methodInfo, parameters); // methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" // Create function if (methodCall.Type == typeof(void)) { var lambda = Expression.Lambda(methodCall, targetParameter, parametersParameter); var voidExecutor = lambda.Compile(); return WrapVoidMethod(voidExecutor); } else { // must coerce methodCall to match ActionExecutor signature var castMethodCall = Expression.Convert(methodCall, typeof(object)); var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); return lambda.Compile(); } } private static MethodExecutor WrapVoidMethod(VoidMethodExecutor executor) { return delegate (object target, object[] parameters) { executor(target, parameters); return null; }; } 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(); 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 var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); var 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>( 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>( Expression.MakeMemberAccess( Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType), awaitableInfo.AwaiterIsCompletedProperty), isCompletedParam).Compile(); var getResultParam = Expression.Parameter(typeof(object), "awaiter"); Func 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>( 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>( 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>( Expression.Call( Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType), awaitableInfo.AwaiterOnCompletedMethod, onCompletedParam2), onCompletedParam1, onCompletedParam2).Compile(); 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>( 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))); var lambda = Expression.Lambda(returnValueExpression, targetParameter, parametersParameter); return lambda.Compile(); } } }