|
19 | 19 | using System.Text; |
20 | 20 | using System.Management.Automation.Internal; |
21 | 21 | using System.Management.Automation.Runspaces; |
22 | | -using System.Diagnostics.CodeAnalysis; // for fxcop |
| 22 | +using System.Diagnostics.CodeAnalysis; |
23 | 23 | using Dbg = System.Management.Automation.Diagnostics; |
24 | 24 | using MethodCacheEntry = System.Management.Automation.DotNetAdapter.MethodCacheEntry; |
25 | 25 |
|
@@ -3202,49 +3202,6 @@ private static Delegate ConvertScriptBlockToDelegate(object valueToConvert, |
3202 | 3202 | valueToConvert.ToString(), resultType.ToString(), exception.Message); |
3203 | 3203 | } |
3204 | 3204 |
|
3205 | | - private static Delegate ConvertPSMethodInfoToDelegate(object valueToConvert, |
3206 | | - Type resultType, |
3207 | | - bool recurse, |
3208 | | - PSObject originalValueToConvert, |
3209 | | - IFormatProvider formatProvider, |
3210 | | - TypeTable backupTable) |
3211 | | - { |
3212 | | - // We can only possibly convert PSMethod instance of the type PSMethod<T>. |
3213 | | - // Such a PSMethod essentially represents a set of .NET method overloads. |
3214 | | - var psMethod = (PSMethod)valueToConvert; |
3215 | | - |
3216 | | - try |
3217 | | - { |
3218 | | - var methods = (MethodCacheEntry)psMethod.adapterData; |
3219 | | - var isStatic = psMethod.instance is Type; |
3220 | | - var targetMethodInfo = resultType.GetMethod("Invoke"); |
3221 | | - var comparator = new DelegateArgsComparator(targetMethodInfo); |
3222 | | - |
3223 | | - foreach (var methodInformation in methods.methodInformationStructures) |
3224 | | - { |
3225 | | - var candidate = (MethodInfo)methodInformation.method; |
3226 | | - if (comparator.SignatureMatches(candidate.ReturnType, candidate.GetParameters())) |
3227 | | - { |
3228 | | - return isStatic ? candidate.CreateDelegate(resultType) |
3229 | | - : candidate.CreateDelegate(resultType, psMethod.instance); |
3230 | | - } |
3231 | | - } |
3232 | | - } |
3233 | | - catch (Exception e) |
3234 | | - { |
3235 | | - typeConversion.WriteLine("PSMethod to Delegate exception: \"{0}\".", e.Message); |
3236 | | - throw new PSInvalidCastException("InvalidCastExceptionPSMethodToDelegate", e, |
3237 | | - ExtendedTypeSystem.InvalidCastExceptionWithInnerException, |
3238 | | - valueToConvert.ToString(), resultType.ToString(), e.Message); |
3239 | | - } |
3240 | | - |
3241 | | - var msg = String.Format(ExtendedTypeSystem.PSMethodToDelegateNoMatchingOverLoad, psMethod, resultType); |
3242 | | - typeConversion.WriteLine($"PSMethod to Delegate exception: \"{msg}\"."); |
3243 | | - throw new PSInvalidCastException("InvalidCastExceptionPSMethodToDelegate", null, |
3244 | | - ExtendedTypeSystem.InvalidCastExceptionWithInnerException, |
3245 | | - valueToConvert.ToString(), resultType.ToString(), msg); |
3246 | | - } |
3247 | | - |
3248 | 3205 | private static object ConvertToNullable(object valueToConvert, |
3249 | 3206 | Type resultType, |
3250 | 3207 | bool recursion, |
@@ -3486,6 +3443,65 @@ private static object ConvertEnumerableToEnum(object valueToConvert, |
3486 | 3443 | return ConvertStringToEnum(sbResult.ToString(), resultType, recursion, originalValueToConvert, formatProvider, backupTable); |
3487 | 3444 | } |
3488 | 3445 |
|
| 3446 | + private class PSMethodToDelegateConverter |
| 3447 | + { |
| 3448 | + // Index of the matching overload method. |
| 3449 | + private readonly int _matchIndex; |
| 3450 | + // Size of the cache. It's rare to have more than 10 overloads for a method. |
| 3451 | + private const int CacheSize = 10; |
| 3452 | + private static readonly PSMethodToDelegateConverter[] s_converterCache = new PSMethodToDelegateConverter[CacheSize]; |
| 3453 | + |
| 3454 | + private PSMethodToDelegateConverter(int matchIndex) |
| 3455 | + { |
| 3456 | + _matchIndex = matchIndex; |
| 3457 | + } |
| 3458 | + |
| 3459 | + internal static PSMethodToDelegateConverter GetConverter(int matchIndex) |
| 3460 | + { |
| 3461 | + if (matchIndex >= CacheSize) { return new PSMethodToDelegateConverter(matchIndex); } |
| 3462 | + |
| 3463 | + var result = s_converterCache[matchIndex]; |
| 3464 | + if (result == null) |
| 3465 | + { |
| 3466 | + // If the cache entry is null, generate a new instance for the cache slot. |
| 3467 | + var converter = new PSMethodToDelegateConverter(matchIndex); |
| 3468 | + Threading.Interlocked.CompareExchange(ref s_converterCache[matchIndex], converter, null); |
| 3469 | + result = s_converterCache[matchIndex]; |
| 3470 | + } |
| 3471 | + |
| 3472 | + return result; |
| 3473 | + } |
| 3474 | + |
| 3475 | + internal Delegate Convert(object valueToConvert, |
| 3476 | + Type resultType, |
| 3477 | + bool recursion, |
| 3478 | + PSObject originalValueToConvert, |
| 3479 | + IFormatProvider formatProvider, |
| 3480 | + TypeTable backupTable) |
| 3481 | + { |
| 3482 | + // We can only possibly convert PSMethod instance of the type PSMethod<T>. |
| 3483 | + // Such a PSMethod essentially represents a set of .NET method overloads. |
| 3484 | + var psMethod = (PSMethod)valueToConvert; |
| 3485 | + |
| 3486 | + try |
| 3487 | + { |
| 3488 | + var methods = (MethodCacheEntry)psMethod.adapterData; |
| 3489 | + var isStatic = psMethod.instance is Type; |
| 3490 | + |
| 3491 | + var candidate = (MethodInfo)methods.methodInformationStructures[_matchIndex].method; |
| 3492 | + return isStatic ? candidate.CreateDelegate(resultType) |
| 3493 | + : candidate.CreateDelegate(resultType, psMethod.instance); |
| 3494 | + } |
| 3495 | + catch (Exception e) |
| 3496 | + { |
| 3497 | + typeConversion.WriteLine("PSMethod to Delegate exception: \"{0}\".", e.Message); |
| 3498 | + throw new PSInvalidCastException("InvalidCastExceptionPSMethodToDelegate", e, |
| 3499 | + ExtendedTypeSystem.InvalidCastExceptionWithInnerException, |
| 3500 | + valueToConvert.ToString(), resultType.ToString(), e.Message); |
| 3501 | + } |
| 3502 | + } |
| 3503 | + } |
| 3504 | + |
3489 | 3505 | private class ConvertViaParseMethod |
3490 | 3506 | { |
3491 | 3507 | // TODO - use an ETS wrapper that generates a dynamic method |
@@ -4772,66 +4788,148 @@ private static ConversionData FigureLanguageConversion(Type fromType, Type toTyp |
4772 | 4788 | return CacheConversion<object>(fromType, toType, LanguagePrimitives.ConvertIntegerToEnum, ConversionRank.Language); |
4773 | 4789 | } |
4774 | 4790 |
|
4775 | | - if (fromType.IsSubclassOf(typeof(PSMethod)) && toType.IsSubclassOf(typeof(Delegate))) |
| 4791 | + if (fromType.IsSubclassOf(typeof(PSMethod)) && toType.IsSubclassOf(typeof(Delegate)) && !toType.IsAbstract) |
4776 | 4792 | { |
4777 | | - var mi = toType.GetMethod("Invoke"); |
4778 | | - |
4779 | | - var comparator = new DelegateArgsComparator(mi); |
| 4793 | + var targetMethod = toType.GetMethod("Invoke"); |
| 4794 | + var comparator = new SignatureComparator(targetMethod); |
4780 | 4795 | var signatureEnumerator = new PSMethodSignatureEnumerator(fromType); |
| 4796 | + int index = -1, matchedIndex = -1; |
| 4797 | + |
4781 | 4798 | while (signatureEnumerator.MoveNext()) |
4782 | 4799 | { |
4783 | | - var candidate = signatureEnumerator.Current.GetMethod("Invoke"); |
4784 | | - if (comparator.SignatureMatches(candidate.ReturnType, candidate.GetParameters())) |
| 4800 | + index++; |
| 4801 | + var signatureType = signatureEnumerator.Current; |
| 4802 | + // Skip the non-bindable signatures |
| 4803 | + if (signatureType == typeof(Func<PSNonBindableType>)) { continue; } |
| 4804 | + |
| 4805 | + Type[] argumentTypes = signatureType.GenericTypeArguments; |
| 4806 | + if (comparator.ProjectedSignatureMatchesTarget(argumentTypes, out bool signaturesMatchExactly)) |
4785 | 4807 | { |
4786 | | - return CacheConversion<Delegate>(fromType, toType, LanguagePrimitives.ConvertPSMethodInfoToDelegate, ConversionRank.Language); |
| 4808 | + if (signaturesMatchExactly) |
| 4809 | + { |
| 4810 | + // We prefer the signature that exactly matches the target delegate. |
| 4811 | + matchedIndex = index; |
| 4812 | + break; |
| 4813 | + } |
| 4814 | + |
| 4815 | + // If there is no exact match, then we use the first compatible signature we found. |
| 4816 | + if (matchedIndex == -1) { matchedIndex = index; } |
4787 | 4817 | } |
4788 | 4818 | } |
| 4819 | + |
| 4820 | + if (matchedIndex > -1) |
| 4821 | + { |
| 4822 | + // We got the index of the matching method signature based on the PSMethod<..> type. |
| 4823 | + // Signatures in PSMethod<..> type were constructed based on the array of method overloads, |
| 4824 | + // in the exact order. So we can use this index directly to locate the matching overload in |
| 4825 | + // the converter, without having to compare the signature again. |
| 4826 | + var converter = PSMethodToDelegateConverter.GetConverter(matchedIndex); |
| 4827 | + return CacheConversion<Delegate>(fromType, toType, converter.Convert, ConversionRank.Language); |
| 4828 | + } |
4789 | 4829 | } |
4790 | 4830 |
|
4791 | 4831 | return null; |
4792 | 4832 | } |
4793 | 4833 |
|
4794 | | - struct DelegateArgsComparator |
| 4834 | + private struct SignatureComparator |
4795 | 4835 | { |
4796 | | - private readonly ParameterInfo[] _targetParametersInfos; |
4797 | | - private readonly Type _returnType; |
4798 | | - |
4799 | | - public DelegateArgsComparator(MethodInfo targetMethodInfo) |
| 4836 | + enum TypeMatchingContext |
4800 | 4837 | { |
4801 | | - _returnType = targetMethodInfo.ReturnType; |
4802 | | - _targetParametersInfos = targetMethodInfo.GetParameters(); |
| 4838 | + ReturnType, |
| 4839 | + ParameterType, |
| 4840 | + OutParameterType |
4803 | 4841 | } |
4804 | 4842 |
|
4805 | | - public bool SignatureMatches(Type returnType, ParameterInfo[] arguments) |
| 4843 | + private readonly ParameterInfo[] targetParameters; |
| 4844 | + private readonly Type targetReturnType; |
| 4845 | + |
| 4846 | + internal SignatureComparator(MethodInfo targetMethodInfo) |
4806 | 4847 | { |
4807 | | - return ReturnTypeMatches(returnType) && ParameterTypesMatches(arguments); |
| 4848 | + targetReturnType = targetMethodInfo.ReturnType; |
| 4849 | + targetParameters = targetMethodInfo.GetParameters(); |
4808 | 4850 | } |
4809 | 4851 |
|
4810 | | - private bool ReturnTypeMatches(Type returnType) |
4811 | | - { |
4812 | | - return PSMethod.MatchesPSMethodProjectedType(_returnType, returnType, testAssignment: true); |
| 4852 | + /// <summary> |
| 4853 | + /// Check if a projected signature matches the target method. |
| 4854 | + /// </summary> |
| 4855 | + /// <param name="argumentTypes"> |
| 4856 | + /// The type arguments from the metadata type 'Func[..]' that represents the projected signature. |
| 4857 | + /// It contains the return type as the last item in the array. |
| 4858 | + /// </param> |
| 4859 | + /// <param name="signaturesMatchExactly"> |
| 4860 | + /// Set by this method to indicate if it's an exact match. |
| 4861 | + /// </param> |
| 4862 | + internal bool ProjectedSignatureMatchesTarget(Type[] argumentTypes, out bool signaturesMatchExactly) |
| 4863 | + { |
| 4864 | + signaturesMatchExactly = false; |
| 4865 | + int length = argumentTypes.Length; |
| 4866 | + if (length != targetParameters.Length + 1) { return false; } |
| 4867 | + |
| 4868 | + bool typesMatchExactly, allTypesMatchExactly; |
| 4869 | + Type sourceReturnType = argumentTypes[length - 1]; |
| 4870 | + |
| 4871 | + if (ProjectedTypeMatchesTargetType(sourceReturnType, targetReturnType, TypeMatchingContext.ReturnType, out typesMatchExactly)) |
| 4872 | + { |
| 4873 | + allTypesMatchExactly = typesMatchExactly; |
| 4874 | + for (int i = 0; i < targetParameters.Length; i++) |
| 4875 | + { |
| 4876 | + var targetParam = targetParameters[i]; |
| 4877 | + var sourceType = argumentTypes[i]; |
| 4878 | + var matchContext = targetParam.IsOut ? TypeMatchingContext.OutParameterType : TypeMatchingContext.ParameterType; |
| 4879 | + |
| 4880 | + if (!ProjectedTypeMatchesTargetType(sourceType, targetParam.ParameterType, matchContext, out typesMatchExactly)) |
| 4881 | + { |
| 4882 | + return false; |
| 4883 | + } |
| 4884 | + allTypesMatchExactly &= typesMatchExactly; |
| 4885 | + } |
| 4886 | + |
| 4887 | + signaturesMatchExactly = allTypesMatchExactly; |
| 4888 | + return true; |
| 4889 | + } |
| 4890 | + |
| 4891 | + return false; |
4813 | 4892 | } |
4814 | 4893 |
|
4815 | | - private bool ParameterTypesMatches(ParameterInfo[] arguments) |
| 4894 | + private static bool ProjectedTypeMatchesTargetType(Type sourceType, Type targetType, TypeMatchingContext matchContext, out bool matchExactly) |
4816 | 4895 | { |
4817 | | - var argsCount = _targetParametersInfos.Length; |
4818 | | - // void is encoded as typeof(VOID) in the PSMethod<MethodGroup<>> as the last parameter |
4819 | | - if (arguments.Length != argsCount) |
| 4896 | + matchExactly = false; |
| 4897 | + if (targetType.IsByRef || targetType.IsPointer) |
4820 | 4898 | { |
| 4899 | + if (!sourceType.IsGenericType) { return false; } |
| 4900 | + |
| 4901 | + var sourceTypeDef = sourceType.GetGenericTypeDefinition(); |
| 4902 | + bool isOutParameter = matchContext == TypeMatchingContext.OutParameterType; |
| 4903 | + |
| 4904 | + if (targetType.IsByRef && sourceTypeDef == (isOutParameter ? typeof(PSOutParameter<>) : typeof(PSReference<>)) || |
| 4905 | + targetType.IsPointer && sourceTypeDef == typeof(PSPointer<>)) |
| 4906 | + { |
| 4907 | + // For ref/out parameter types and pointer types, the element types need to match exactly. |
| 4908 | + if (targetType.GetElementType() == sourceType.GenericTypeArguments[0]) |
| 4909 | + { |
| 4910 | + matchExactly = true; |
| 4911 | + return true; |
| 4912 | + } |
| 4913 | + } |
4821 | 4914 | return false; |
4822 | 4915 | } |
4823 | | - for (int i = 0; i < arguments.Length; i++) |
| 4916 | + |
| 4917 | + if (targetType == sourceType || |
| 4918 | + targetType == typeof(void) && sourceType == typeof(VOID) || |
| 4919 | + targetType == typeof(TypedReference) && sourceType == typeof(PSTypedReference)) |
4824 | 4920 | { |
4825 | | - var arg = arguments[i]; |
4826 | | - var argType = arg.ParameterType; |
4827 | | - var targetParamType = _targetParametersInfos[i].ParameterType; |
4828 | | - var isOut = (arg.Attributes | ParameterAttributes.Out) == ParameterAttributes.Out; |
4829 | | - if (!PSMethod.MatchesPSMethodProjectedType(targetParamType, argType, isOut: isOut)) |
4830 | | - { |
4831 | | - return false; |
4832 | | - } |
| 4921 | + matchExactly = true; |
| 4922 | + return true; |
4833 | 4923 | } |
4834 | | - return true; |
| 4924 | + |
| 4925 | + if (targetType == typeof(void) || targetType == typeof(TypedReference)) |
| 4926 | + { |
| 4927 | + return false; |
| 4928 | + } |
| 4929 | + |
| 4930 | + return matchContext == TypeMatchingContext.ReturnType |
| 4931 | + ? targetType.IsAssignableFrom(sourceType) |
| 4932 | + : sourceType.IsAssignableFrom(targetType); |
4835 | 4933 | } |
4836 | 4934 | } |
4837 | 4935 |
|
|
0 commit comments