From bbf0ebdd91190dc412cf38a44d445035441ece9f Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 11:04:12 -0800 Subject: [PATCH] Remove Microsoft.Extensions.ObjectMethodExecutor.Sources \n\nCommit migrated from https://github.com/dotnet/extensions/commit/71a672e28701ea6f3cc4034ba11891f7ce38189f --- .../ObjectMethodExecutor/AwaitableInfo.cs | 127 ---- .../CoercedAwaitableInfo.cs | 55 -- .../ObjectMethodExecutor.cs | 340 ---------- .../ObjectMethodExecutorAwaitable.cs | 114 ---- .../ObjectMethodExecutorFSharpSupport.cs | 151 ----- .../Shared.Tests/ObjectMethodExecutorTest.cs | 634 ------------------ 6 files changed, 1421 deletions(-) delete mode 100644 src/Shared/ObjectMethodExecutor/AwaitableInfo.cs delete mode 100644 src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs delete mode 100644 src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs delete mode 100644 src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs delete mode 100644 src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs delete mode 100644 src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs diff --git a/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs b/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs deleted file mode 100644 index 431b83a6e5..0000000000 --- a/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs +++ /dev/null @@ -1,127 +0,0 @@ -// 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.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; - -namespace Microsoft.Extensions.Internal -{ - internal struct AwaitableInfo - { - public Type AwaiterType { get; } - public PropertyInfo AwaiterIsCompletedProperty { get; } - public MethodInfo AwaiterGetResultMethod { get; } - public MethodInfo AwaiterOnCompletedMethod { get; } - public MethodInfo AwaiterUnsafeOnCompletedMethod { get; } - public Type ResultType { get; } - public MethodInfo GetAwaiterMethod { get; } - - public AwaitableInfo( - Type awaiterType, - PropertyInfo awaiterIsCompletedProperty, - MethodInfo awaiterGetResultMethod, - MethodInfo awaiterOnCompletedMethod, - MethodInfo awaiterUnsafeOnCompletedMethod, - Type resultType, - MethodInfo getAwaiterMethod) - { - AwaiterType = awaiterType; - AwaiterIsCompletedProperty = awaiterIsCompletedProperty; - AwaiterGetResultMethod = awaiterGetResultMethod; - AwaiterOnCompletedMethod = awaiterOnCompletedMethod; - AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod; - ResultType = resultType; - GetAwaiterMethod = getAwaiterMethod; - } - - public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo) - { - // Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347 - - // Awaitable must have method matching "object GetAwaiter()" - var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m => - m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase) - && m.GetParameters().Length == 0 - && m.ReturnType != null); - if (getAwaiterMethod == null) - { - awaitableInfo = default(AwaitableInfo); - return false; - } - - var awaiterType = getAwaiterMethod.ReturnType; - - // Awaiter must have property matching "bool IsCompleted { get; }" - var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p => - p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase) - && p.PropertyType == typeof(bool) - && p.GetMethod != null); - if (isCompletedProperty == null) - { - awaitableInfo = default(AwaitableInfo); - return false; - } - - // Awaiter must implement INotifyCompletion - var awaiterInterfaces = awaiterType.GetInterfaces(); - var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion)); - if (!implementsINotifyCompletion) - { - awaitableInfo = default(AwaitableInfo); - return false; - } - - // INotifyCompletion supplies a method matching "void OnCompleted(Action action)" - var iNotifyCompletionMap = awaiterType - .GetTypeInfo() - .GetRuntimeInterfaceMap(typeof(INotifyCompletion)); - var onCompletedMethod = iNotifyCompletionMap.InterfaceMethods.Single(m => - m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase) - && m.ReturnType == typeof(void) - && m.GetParameters().Length == 1 - && m.GetParameters()[0].ParameterType == typeof(Action)); - - // Awaiter optionally implements ICriticalNotifyCompletion - var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion)); - MethodInfo unsafeOnCompletedMethod; - if (implementsICriticalNotifyCompletion) - { - // ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)" - var iCriticalNotifyCompletionMap = awaiterType - .GetTypeInfo() - .GetRuntimeInterfaceMap(typeof(ICriticalNotifyCompletion)); - unsafeOnCompletedMethod = iCriticalNotifyCompletionMap.InterfaceMethods.Single(m => - m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase) - && m.ReturnType == typeof(void) - && m.GetParameters().Length == 1 - && m.GetParameters()[0].ParameterType == typeof(Action)); - } - else - { - unsafeOnCompletedMethod = null; - } - - // Awaiter must have method matching "void GetResult" or "T GetResult()" - var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m => - m.Name.Equals("GetResult") - && m.GetParameters().Length == 0); - if (getResultMethod == null) - { - awaitableInfo = default(AwaitableInfo); - return false; - } - - awaitableInfo = new AwaitableInfo( - awaiterType, - isCompletedProperty, - getResultMethod, - onCompletedMethod, - unsafeOnCompletedMethod, - getResultMethod.ReturnType, - getAwaiterMethod); - return true; - } - } -} diff --git a/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs b/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs deleted file mode 100644 index 4e48ef09a1..0000000000 --- a/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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.Linq.Expressions; - -namespace Microsoft.Extensions.Internal -{ - internal struct CoercedAwaitableInfo - { - public AwaitableInfo AwaitableInfo { get; } - public Expression CoercerExpression { get; } - public Type CoercerResultType { get; } - public bool RequiresCoercion => CoercerExpression != null; - - public CoercedAwaitableInfo(AwaitableInfo awaitableInfo) - { - AwaitableInfo = awaitableInfo; - CoercerExpression = null; - CoercerResultType = null; - } - - public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, AwaitableInfo coercedAwaitableInfo) - { - CoercerExpression = coercerExpression; - CoercerResultType = coercerResultType; - AwaitableInfo = coercedAwaitableInfo; - } - - 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. - 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; - } - } -} diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs deleted file mode 100644 index f8e5b70f0d..0000000000 --- a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs +++ /dev/null @@ -1,340 +0,0 @@ -// 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(); - } - } -} diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs deleted file mode 100644 index 7509b86b2b..0000000000 --- a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs +++ /dev/null @@ -1,114 +0,0 @@ -// 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.Runtime.CompilerServices; - -namespace Microsoft.Extensions.Internal -{ - /// - /// Provides a common awaitable structure that can - /// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an - /// application-defined custom awaitable. - /// - internal struct ObjectMethodExecutorAwaitable - { - private readonly object _customAwaitable; - private readonly Func _getAwaiterMethod; - private readonly Func _isCompletedMethod; - private readonly Func _getResultMethod; - private readonly Action _onCompletedMethod; - private readonly Action _unsafeOnCompletedMethod; - - // Perf note: since we're requiring the customAwaitable to be supplied here as an object, - // this will trigger a further allocation if it was a value type (i.e., to box it). We can't - // fix this by making the customAwaitable type generic, because the calling code typically - // does not know the type of the awaitable/awaiter at compile-time anyway. - // - // However, we could fix it by not passing the customAwaitable here at all, and instead - // passing a func that maps directly from the target object (e.g., controller instance), - // target method (e.g., action method info), and params array to the custom awaiter in the - // GetAwaiter() method below. In effect, by delaying the actual method call until the - // upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance. - // This optimization is not currently implemented because: - // [1] It would make no difference when the awaitable was an object type, which is - // by far the most common scenario (e.g., System.Task). - // [2] It would be complex - we'd need some kind of object pool to track all the parameter - // arrays until we needed to use them in GetAwaiter(). - // We can reconsider this in the future if there's a need to optimize for ValueTask - // or other value-typed awaitables. - - public ObjectMethodExecutorAwaitable( - object customAwaitable, - Func getAwaiterMethod, - Func isCompletedMethod, - Func getResultMethod, - Action onCompletedMethod, - Action unsafeOnCompletedMethod) - { - _customAwaitable = customAwaitable; - _getAwaiterMethod = getAwaiterMethod; - _isCompletedMethod = isCompletedMethod; - _getResultMethod = getResultMethod; - _onCompletedMethod = onCompletedMethod; - _unsafeOnCompletedMethod = unsafeOnCompletedMethod; - } - - public Awaiter GetAwaiter() - { - var customAwaiter = _getAwaiterMethod(_customAwaitable); - return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, _unsafeOnCompletedMethod); - } - - public struct Awaiter : ICriticalNotifyCompletion - { - private readonly object _customAwaiter; - private readonly Func _isCompletedMethod; - private readonly Func _getResultMethod; - private readonly Action _onCompletedMethod; - private readonly Action _unsafeOnCompletedMethod; - - public Awaiter( - object customAwaiter, - Func isCompletedMethod, - Func getResultMethod, - Action onCompletedMethod, - Action unsafeOnCompletedMethod) - { - _customAwaiter = customAwaiter; - _isCompletedMethod = isCompletedMethod; - _getResultMethod = getResultMethod; - _onCompletedMethod = onCompletedMethod; - _unsafeOnCompletedMethod = unsafeOnCompletedMethod; - } - - public bool IsCompleted => _isCompletedMethod(_customAwaiter); - - public object GetResult() => _getResultMethod(_customAwaiter); - - public void OnCompleted(Action continuation) - { - _onCompletedMethod(_customAwaiter, continuation); - } - - public void UnsafeOnCompleted(Action continuation) - { - // If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted. - // If not, fall back on using its OnCompleted. - // - // Why this is safe: - // - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it - // needs the execution context to be preserved (which it signals by calling OnCompleted), or - // that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not* - // to preserve and restore the context, so we prefer that where possible. - // - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted, - // there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen - // if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to - // pass the call on to the underlying awaitable's OnCompleted method. - - var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod; - underlyingMethodToUse(_customAwaiter, continuation); - } - } - } -} \ No newline at end of file diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs deleted file mode 100644 index 2198c0ce45..0000000000 --- a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs +++ /dev/null @@ -1,151 +0,0 @@ -// 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.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.Internal -{ - /// - /// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying - /// an for mapping instances of that type to a C# awaitable. - /// - /// - /// The main design goal here is to avoid taking a compile-time dependency on - /// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references - /// to FSharp types have to be constructed dynamically at runtime. - /// - internal static class ObjectMethodExecutorFSharpSupport - { - private static object _fsharpValuesCacheLock = new object(); - private static Assembly _fsharpCoreAssembly; - private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod; - private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty; - private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty; - - public static bool TryBuildCoercerFromFSharpAsyncToAwaitable( - Type possibleFSharpAsyncType, - out Expression coerceToAwaitableExpression, - out Type awaitableType) - { - var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType - ? possibleFSharpAsyncType.GetGenericTypeDefinition() - : null; - - if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType)) - { - coerceToAwaitableExpression = null; - awaitableType = null; - return false; - } - - var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single(); - awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType); - - // coerceToAwaitableExpression = (object fsharpAsync) => - // { - // return (object)FSharpAsync.StartAsTask( - // (Microsoft.FSharp.Control.FSharpAsync)fsharpAsync, - // FSharpOption.None, - // FSharpOption.None); - // }; - var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod - .MakeGenericMethod(awaiterResultType); - var coerceToAwaitableParam = Expression.Parameter(typeof(object)); - coerceToAwaitableExpression = Expression.Lambda( - Expression.Convert( - Expression.Call( - startAsTaskClosedMethod, - Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType), - Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty), - Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)), - typeof(object)), - coerceToAwaitableParam); - - return true; - } - - private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType) - { - var typeFullName = possibleFSharpAsyncGenericType?.FullName; - if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal)) - { - return false; - } - - lock (_fsharpValuesCacheLock) - { - if (_fsharpCoreAssembly != null) - { - // Since we've already found the real FSharpAsync.Core assembly, we just have - // to check that the supplied FSharpAsync`1 type is the one from that assembly. - return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly; - } - else - { - // We'll keep trying to find the FSharp types/values each time any type called - // FSharpAsync`1 is supplied. - return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType); - } - } - } - - private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType) - { - var assembly = possibleFSharpAsyncGenericType.Assembly; - var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1"); - var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"); - - if (fsharpOptionType == null || fsharpAsyncType == null) - { - return false; - } - - // Get a reference to FSharpOption.None - var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType - .MakeGenericType(typeof(TaskCreationOptions)); - _fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType - .GetTypeInfo() - .GetRuntimeProperty("None"); - - // Get a reference to FSharpOption.None - var fsharpOptionOfCancellationTokenType = fsharpOptionType - .MakeGenericType(typeof(CancellationToken)); - _fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType - .GetTypeInfo() - .GetRuntimeProperty("None"); - - // Get a reference to FSharpAsync.StartAsTask<> - var fsharpAsyncMethods = fsharpAsyncType - .GetRuntimeMethods() - .Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal)); - foreach (var candidateMethodInfo in fsharpAsyncMethods) - { - var parameters = candidateMethodInfo.GetParameters(); - if (parameters.Length == 3 - && TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType) - && parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType - && parameters[2].ParameterType == fsharpOptionOfCancellationTokenType) - { - // This really does look like the correct method (and hence assembly). - _fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo; - _fsharpCoreAssembly = assembly; - break; - } - } - - return _fsharpCoreAssembly != null; - } - - private static bool TypesHaveSameIdentity(Type type1, Type type2) - { - return type1.Assembly == type2.Assembly - && string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal) - && string.Equals(type1.Name, type2.Name, StringComparison.Ordinal); - } - } -} diff --git a/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs b/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs deleted file mode 100644 index 1c26ef1de1..0000000000 --- a/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs +++ /dev/null @@ -1,634 +0,0 @@ -// 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 Microsoft.FSharp.Control; -using Microsoft.FSharp.Core; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class ObjectMethodExecutorTest - { - private TestObject _targetObject = new TestObject(); - private TypeInfo targetTypeInfo = typeof(TestObject).GetTypeInfo(); - - [Fact] - public void ExecuteValueMethod() - { - var executor = GetExecutorForMethod("ValueMethod"); - var result = executor.Execute( - _targetObject, - new object[] { 10, 20 }); - Assert.False(executor.IsMethodAsync); - Assert.Equal(30, (int)result); - } - - [Fact] - public void ExecuteVoidValueMethod() - { - var executor = GetExecutorForMethod("VoidValueMethod"); - var result = executor.Execute( - _targetObject, - new object[] { 10 }); - Assert.False(executor.IsMethodAsync); - Assert.Null(result); - } - - [Fact] - public void ExecuteValueMethodWithReturnType() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnType"); - var result = executor.Execute( - _targetObject, - new object[] { 10 }); - var resultObject = Assert.IsType(result); - Assert.False(executor.IsMethodAsync); - Assert.Equal("Hello", resultObject.value); - } - - [Fact] - public void ExecuteValueMethodUpdateValue() - { - var executor = GetExecutorForMethod("ValueMethodUpdateValue"); - var parameter = new TestObject(); - var result = executor.Execute( - _targetObject, - new object[] { parameter }); - var resultObject = Assert.IsType(result); - Assert.False(executor.IsMethodAsync); - Assert.Equal("HelloWorld", resultObject.value); - } - - [Fact] - public void ExecuteValueMethodWithReturnTypeThrowsException() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsException"); - var parameter = new TestObject(); - Assert.False(executor.IsMethodAsync); - Assert.Throws( - () => executor.Execute( - _targetObject, - new object[] { parameter })); - } - - [Fact] - public async Task ExecuteValueMethodAsync() - { - var executor = GetExecutorForMethod("ValueMethodAsync"); - var result = await executor.ExecuteAsync( - _targetObject, - new object[] { 10, 20 }); - Assert.True(executor.IsMethodAsync); - Assert.Equal(30, (int)result); - } - - [Fact] - public async Task ExecuteValueMethodWithReturnTypeAsync() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnTypeAsync"); - var result = await executor.ExecuteAsync( - _targetObject, - new object[] { 10 }); - var resultObject = Assert.IsType(result); - Assert.True(executor.IsMethodAsync); - Assert.Equal("Hello", resultObject.value); - } - - [Fact] - public async Task ExecuteValueMethodUpdateValueAsync() - { - var executor = GetExecutorForMethod("ValueMethodUpdateValueAsync"); - var parameter = new TestObject(); - var result = await executor.ExecuteAsync( - _targetObject, - new object[] { parameter }); - var resultObject = Assert.IsType(result); - Assert.True(executor.IsMethodAsync); - Assert.Equal("HelloWorld", resultObject.value); - } - - [Fact] - public async Task ExecuteValueMethodWithReturnTypeThrowsExceptionAsync() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsExceptionAsync"); - var parameter = new TestObject(); - Assert.True(executor.IsMethodAsync); - await Assert.ThrowsAsync( - async () => await executor.ExecuteAsync( - _targetObject, - new object[] { parameter })); - } - - [Fact] - public async Task ExecuteValueMethodWithReturnVoidThrowsExceptionAsync() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnVoidThrowsExceptionAsync"); - var parameter = new TestObject(); - Assert.True(executor.IsMethodAsync); - await Assert.ThrowsAsync( - async () => await executor.ExecuteAsync( - _targetObject, - new object[] { parameter })); - } - - [Fact] - public void GetDefaultValueForParameters_ReturnsSuppliedValues() - { - var suppliedDefaultValues = new object[] { 123, "test value" }; - var executor = GetExecutorForMethod("MethodWithMultipleParameters", suppliedDefaultValues); - Assert.Equal(suppliedDefaultValues[0], executor.GetDefaultValueForParameter(0)); - Assert.Equal(suppliedDefaultValues[1], executor.GetDefaultValueForParameter(1)); - Assert.Throws(() => executor.GetDefaultValueForParameter(2)); - } - - [Fact] - public void GetDefaultValueForParameters_ThrowsIfNoneWereSupplied() - { - var executor = GetExecutorForMethod("MethodWithMultipleParameters"); - Assert.Throws(() => executor.GetDefaultValueForParameter(0)); - } - - [Fact] - public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); - - // Act - var result = await (TestAwaitable)executor.Execute(_targetObject, new object[] { "Hello", 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(TestObject), executor.AsyncResultType); - Assert.NotNull(result); - Assert.Equal("Hello 123", result.value); - } - - [Fact] - public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); - - // Act - var result = await (TestAwaitable)executor.Execute(_targetObject, new object[] { 123, 456 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(int), executor.AsyncResultType); - Assert.Equal(579, result); - } - - [Fact] - public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { "Hello", 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(TestObject), executor.AsyncResultType); - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Hello 123", ((TestObject)result).value); - } - - [Fact] - public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { 123, 456 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(int), executor.AsyncResultType); - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal(579, (int)result); - } - - [Fact] - public async void TargetMethodReturningAwaitableOfVoidType_CanInvokeViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("VoidValueMethodAsync"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(void), executor.AsyncResultType); - Assert.Null(result); - } - - [Fact] - public async void TargetMethodReturningAwaitableWithICriticalNotifyCompletion_UsesUnsafeOnCompleted() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableWithICriticalNotifyCompletion"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[0]); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("Used UnsafeOnCompleted", (string)result); - } - - [Fact] - public async void TargetMethodReturningAwaitableWithoutICriticalNotifyCompletion_UsesOnCompleted() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableWithoutICriticalNotifyCompletion"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[0]); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("Used OnCompleted", (string)result); - } - - [Fact] - public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("ValueTaskOfValueType"); - - // Act - var result = await (ValueTask)executor.Execute(_targetObject, new object[] { 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(int), executor.AsyncResultType); - Assert.Equal(123, result); - } - - [Fact] - public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); - - // Act - var result = await (ValueTask)executor.Execute(_targetObject, new object[] { "test result" }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("test result", result); - } - - [Fact] - public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("ValueTaskOfValueType"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(int), executor.AsyncResultType); - Assert.NotNull(result); - Assert.Equal(123, (int)result); - } - - [Fact] - public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("test result", result); - } - - [Fact] - public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("FSharpAsyncMethod"); - - // Act - var fsharpAsync = (FSharpAsync)executor.Execute(_targetObject, new object[] { "test result" }); - var result = await FSharpAsync.StartAsTask(fsharpAsync, - FSharpOption.None, - FSharpOption.None); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("test result", result); - } - - [Fact] - public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); - - // Act - var fsharpAsync = (FSharpAsync)executor.Execute(_targetObject, new object[] { "test result" }); - var resultTask = FSharpAsync.StartAsTask(fsharpAsync, - FSharpOption.None, - FSharpOption.None); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - - var exception = await Assert.ThrowsAsync(async () => await resultTask); - Assert.IsType(exception.InnerException); - Assert.Equal("Test exception", exception.InnerException.Message); - } - - [Fact] - public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("FSharpAsyncMethod"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("test result", result); - } - - [Fact] - public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); - - // Act - var resultTask = executor.ExecuteAsync(_targetObject, new object[] { "test result" }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - - var exception = await Assert.ThrowsAsync(async () => await resultTask); - Assert.IsType(exception.InnerException); - Assert.Equal("Test exception", exception.InnerException.Message); - } - - private ObjectMethodExecutor GetExecutorForMethod(string methodName) - { - var method = typeof(TestObject).GetMethod(methodName); - return ObjectMethodExecutor.Create(method, targetTypeInfo); - } - - private ObjectMethodExecutor GetExecutorForMethod(string methodName, object[] parameterDefaultValues) - { - var method = typeof(TestObject).GetMethod(methodName); - return ObjectMethodExecutor.Create(method, targetTypeInfo, parameterDefaultValues); - } - - public class TestObject - { - public string value; - public int ValueMethod(int i, int j) - { - return i + j; - } - - public void VoidValueMethod(int i) - { - - } - - public TestObject ValueMethodWithReturnType(int i) - { - return new TestObject() { value = "Hello" }; ; - } - - public TestObject ValueMethodWithReturnTypeThrowsException(TestObject i) - { - throw new NotImplementedException("Not Implemented Exception"); - } - - public TestObject ValueMethodUpdateValue(TestObject parameter) - { - parameter.value = "HelloWorld"; - return parameter; - } - - public Task ValueMethodAsync(int i, int j) - { - return Task.FromResult(i + j); - } - - public async Task VoidValueMethodAsync(int i) - { - await ValueMethodAsync(3, 4); - } - public Task ValueMethodWithReturnTypeAsync(int i) - { - return Task.FromResult(new TestObject() { value = "Hello" }); - } - - public async Task ValueMethodWithReturnVoidThrowsExceptionAsync(TestObject i) - { - await Task.CompletedTask; - throw new NotImplementedException("Not Implemented Exception"); - } - - public async Task ValueMethodWithReturnTypeThrowsExceptionAsync(TestObject i) - { - await Task.CompletedTask; - throw new NotImplementedException("Not Implemented Exception"); - } - - public Task ValueMethodUpdateValueAsync(TestObject parameter) - { - parameter.value = "HelloWorld"; - return Task.FromResult(parameter); - } - - public TestAwaitable CustomAwaitableOfReferenceTypeAsync( - string input1, - int input2) - { - return new TestAwaitable(new TestObject - { - value = $"{input1} {input2}" - }); - } - - public TestAwaitable CustomAwaitableOfValueTypeAsync( - int input1, - int input2) - { - return new TestAwaitable(input1 + input2); - } - - public TestAwaitableWithICriticalNotifyCompletion CustomAwaitableWithICriticalNotifyCompletion() - { - return new TestAwaitableWithICriticalNotifyCompletion(); - } - - public TestAwaitableWithoutICriticalNotifyCompletion CustomAwaitableWithoutICriticalNotifyCompletion() - { - return new TestAwaitableWithoutICriticalNotifyCompletion(); - } - - public ValueTask ValueTaskOfValueType(int result) - { - return new ValueTask(result); - } - - public ValueTask ValueTaskOfReferenceType(string result) - { - return new ValueTask(result); - } - - public void MethodWithMultipleParameters(int valueTypeParam, string referenceTypeParam) - { - } - - public FSharpAsync FSharpAsyncMethod(string parameter) - { - return FSharpAsync.AwaitTask(Task.FromResult(parameter)); - } - - public FSharpAsync FSharpAsyncFailureMethod(string parameter) - { - return FSharpAsync.AwaitTask( - Task.FromException(new InvalidOperationException("Test exception"))); - } - } - - public class TestAwaitable - { - private T _result; - private bool _isCompleted; - private List _onCompletedCallbacks = new List(); - - public TestAwaitable(T result) - { - _result = result; - - // Simulate a brief delay before completion - ThreadPool.QueueUserWorkItem(_ => - { - Thread.Sleep(100); - SetCompleted(); - }); - } - - private void SetCompleted() - { - _isCompleted = true; - - foreach (var callback in _onCompletedCallbacks) - { - callback(); - } - } - - public TestAwaiter GetAwaiter() - { - return new TestAwaiter(this); - } - - public struct TestAwaiter : INotifyCompletion - { - private TestAwaitable _owner; - - public TestAwaiter(TestAwaitable owner) : this() - { - _owner = owner; - } - - public bool IsCompleted => _owner._isCompleted; - - public void OnCompleted(Action continuation) - { - if (_owner._isCompleted) - { - continuation(); - } - else - { - _owner._onCompletedCallbacks.Add(continuation); - } - } - - public T GetResult() - { - return _owner._result; - } - } - } - - public class TestAwaitableWithICriticalNotifyCompletion - { - public TestAwaiterWithICriticalNotifyCompletion GetAwaiter() - => new TestAwaiterWithICriticalNotifyCompletion(); - } - - public class TestAwaitableWithoutICriticalNotifyCompletion - { - public TestAwaiterWithoutICriticalNotifyCompletion GetAwaiter() - => new TestAwaiterWithoutICriticalNotifyCompletion(); - } - - public class TestAwaiterWithICriticalNotifyCompletion - : CompletionTrackingAwaiterBase, ICriticalNotifyCompletion - { - } - - public class TestAwaiterWithoutICriticalNotifyCompletion - : CompletionTrackingAwaiterBase, INotifyCompletion - { - } - - public class CompletionTrackingAwaiterBase - { - private string _result; - - public bool IsCompleted { get; private set; } - - public string GetResult() => _result; - - public void OnCompleted(Action continuation) - { - _result = "Used OnCompleted"; - IsCompleted = true; - continuation(); - } - - public void UnsafeOnCompleted(Action continuation) - { - _result = "Used UnsafeOnCompleted"; - IsCompleted = true; - continuation(); - } - } - } -}