Add message for AmbiguousMatchException (#84512)
authorMarc Brooks <IDisposable@gmail.com>
Wed, 31 May 2023 17:52:05 +0000 (12:52 -0500)
committerGitHub <noreply@github.com>
Wed, 31 May 2023 17:52:05 +0000 (10:52 -0700)
Add information to what is ambiguously matched when an AmbiguousMatchException is thrown as we know what we found multiple matching items for.

Renames of SR/RESX strings. Removal of `.ToString()` calls. Remove unused strings from RESX RESX string rewording.
Change the exception format string to "found for" style.
Cleanup of DefaultBinder.cs FindMostDerived* to use the local variable.
Remove copy-pasta ThrowHelper.cs instance.
Don't emit the CustomAtrributeData's type
All MemberInfo cases pass a (possibly null) `DeclaringType`.
Co-authored-by: Buyaa Namnan <buyankhishig.namnan@microsoft.com>
29 files changed:
src/coreclr/System.Private.CoreLib/src/System/Attribute.CoreCLR.cs
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Attribute.NativeAot.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/BindingFlagSupport/QueryResult.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ThunkedApis.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.BindingFlags.cs
src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Reflection/Execution/AssemblyBinderImplementation.cs
src/coreclr/nativeaot/System.Private.TypeLoader/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/System/DefaultBinder.cs
src/libraries/System.Private.CoreLib/src/System/Reflection/AmbiguousMatchException.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/AmbiguousImplementationException.cs
src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs
src/libraries/System.Reflection.Context/src/Resources/Strings.resx
src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
src/libraries/System.Reflection.MetadataLoadContext/src/Resources/Strings.resx
src/libraries/System.Reflection.MetadataLoadContext/src/System.Reflection.MetadataLoadContext.csproj
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/DefaultBinder.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/Runtime/BindingFlagSupport/QueryResult.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/CustomAttributes/CustomAttributeHelpers.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/RuntimeTypeInfo.BindingFlags.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/Ecma/EcmaDefinitionType.BindingFlags.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoType.GetInterface.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/ThrowHelper.cs [new file with mode: 0644]
src/libraries/System.Runtime.InteropServices/src/Resources/Strings.resx
src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComAwareEventInfo.cs
src/mono/System.Private.CoreLib/src/System/Attribute.Mono.cs
src/mono/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeTypeBuilder.Mono.cs
src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs

index 258d047..9365f8b 100644 (file)
@@ -515,10 +515,11 @@ namespace System
             if (attrib == null || attrib.Length == 0)
                 return null;
 
+            Attribute match = attrib[0];
             if (attrib.Length == 1)
-                return attrib[0];
+                return match;
 
-            throw new AmbiguousMatchException(SR.RFLCT_AmbigCust);
+            throw ThrowHelper.GetAmbiguousMatchException(match);
         }
 
         #endregion
@@ -614,10 +615,11 @@ namespace System
             if (attrib == null || attrib.Length == 0)
                 return null;
 
+            Attribute match = attrib[0];
             if (attrib.Length == 1)
-                return attrib[0];
+                return match;
 
-            throw new AmbiguousMatchException(SR.RFLCT_AmbigCust);
+            throw ThrowHelper.GetAmbiguousMatchException(match);
         }
 
         #endregion
@@ -683,10 +685,11 @@ namespace System
             if (attrib == null || attrib.Length == 0)
                 return null;
 
+            Attribute match = attrib[0];
             if (attrib.Length == 1)
-                return attrib[0];
+                return match;
 
-            throw new AmbiguousMatchException(SR.RFLCT_AmbigCust);
+            throw ThrowHelper.GetAmbiguousMatchException(match);
         }
 
         #endregion
@@ -752,10 +755,11 @@ namespace System
             if (attrib == null || attrib.Length == 0)
                 return null;
 
+            Attribute match = attrib[0];
             if (attrib.Length == 1)
-                return attrib[0];
+                return match;
 
-            throw new AmbiguousMatchException(SR.RFLCT_AmbigCust);
+            throw ThrowHelper.GetAmbiguousMatchException(match);
         }
 
         #endregion
index 872f9d0..e3590ca 100644 (file)
@@ -2822,9 +2822,7 @@ namespace System
                     {
                         MethodInfo methodInfo = candidates[j];
                         if (!System.DefaultBinder.CompareMethodSig(methodInfo, firstCandidate))
-                        {
-                            throw new AmbiguousMatchException();
-                        }
+                            throw ThrowHelper.GetAmbiguousMatchException(firstCandidate);
                     }
 
                     // All the methods have the exact same name and sig so return the most derived one.
@@ -2878,10 +2876,10 @@ namespace System
             if (types == null || types.Length == 0)
             {
                 // no arguments
+                PropertyInfo firstCandidate = candidates[0];
+
                 if (candidates.Count == 1)
                 {
-                    PropertyInfo firstCandidate = candidates[0];
-
                     if (returnType is not null && !returnType.IsEquivalentTo(firstCandidate.PropertyType))
                         return null;
 
@@ -2891,7 +2889,7 @@ namespace System
                 {
                     if (returnType is null)
                         // if we are here we have no args or property type to select over and we have more than one property with that name
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(firstCandidate);
                 }
             }
 
@@ -2920,7 +2918,7 @@ namespace System
                 if ((bindingAttr & eventInfo.BindingFlags) == eventInfo.BindingFlags)
                 {
                     if (match != null)
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(match);
 
                     match = eventInfo;
                 }
@@ -2950,7 +2948,7 @@ namespace System
                     if (match != null)
                     {
                         if (ReferenceEquals(fieldInfo.DeclaringType, match.DeclaringType))
-                            throw new AmbiguousMatchException();
+                            throw ThrowHelper.GetAmbiguousMatchException(match);
 
                         if ((match.DeclaringType!.IsInterface) && (fieldInfo.DeclaringType!.IsInterface))
                             multipleStaticFieldMatches = true;
@@ -2962,7 +2960,7 @@ namespace System
             }
 
             if (multipleStaticFieldMatches && match!.DeclaringType!.IsInterface)
-                throw new AmbiguousMatchException();
+                throw ThrowHelper.GetAmbiguousMatchException(match);
 
             return match;
         }
@@ -2998,7 +2996,7 @@ namespace System
                 if (FilterApplyType(iface, bindingAttr, name, false, ns))
                 {
                     if (match != null)
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(match);
 
                     match = iface;
                 }
@@ -3027,7 +3025,7 @@ namespace System
                 if (FilterApplyType(nestedType, bindingAttr, name, false, ns))
                 {
                     if (match != null)
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(match);
 
                     match = nestedType;
                 }
index f760461..fe6505f 100644 (file)
@@ -121,7 +121,7 @@ namespace System
                 return null;
             CustomAttributeData result = enumerator.Current;
             if (enumerator.MoveNext())
-                throw new AmbiguousMatchException();
+                throw ThrowHelper.GetAmbiguousMatchException(result);
             return result.Instantiate();
         }
 
index 301fe25..6288b58 100644 (file)
@@ -112,11 +112,10 @@ namespace System.Reflection.Runtime.BindingFlagSupport
                         // Assuming the policy says it's ok to ignore the ambiguity, we're to resolve in favor of the member
                         // declared by the most derived type. Since QueriedMemberLists are sorted in order of decreasing derivation,
                         // that means we let the first match win - unless, of course, they're both the "most derived member".
-                        if (match.DeclaringType.Equals(challenger.DeclaringType))
-                            throw new AmbiguousMatchException();
-
-                        if (!_policies.OkToIgnoreAmbiguity(match, challenger))
-                            throw new AmbiguousMatchException();
+                        // If they're not from same type, we throw if the policy doesn't allow ambiguity.
+                        if (match.DeclaringType.Equals(challenger.DeclaringType) ||
+                            !_policies.OkToIgnoreAmbiguity(match, challenger))
+                            throw ThrowHelper.GetAmbiguousMatchException(match);
                     }
                     else
                     {
index 3e07652..99858e0 100644 (file)
@@ -199,7 +199,7 @@ namespace System.Reflection.Runtime.TypeInfos
                 if (ns != null && !ns.Equals(ifc.Namespace))
                     continue;
                 if (match != null)
-                    throw new AmbiguousMatchException();
+                    throw ThrowHelper.GetAmbiguousMatchException(match);
                 match = ifc;
             }
             return match;
index dcb0ed3..35ae4a9 100644 (file)
@@ -155,9 +155,10 @@ namespace System.Reflection.Runtime.TypeInfos
                 if (types == null || types.Length == 0)
                 {
                     // no arguments
+                    PropertyInfo firstCandidate = candidates[0];
+
                     if (candidates.Count == 1)
                     {
-                        PropertyInfo firstCandidate = candidates[0];
                         if (returnType is not null && !returnType.IsEquivalentTo(firstCandidate.PropertyType))
                             return null;
                         return firstCandidate;
@@ -165,8 +166,10 @@ namespace System.Reflection.Runtime.TypeInfos
                     else
                     {
                         if (returnType is null)
+                        {
                             // if we are here we have no args or property type to select over and we have more than one property with that name
-                            throw new AmbiguousMatchException();
+                            throw ThrowHelper.GetAmbiguousMatchException(firstCandidate);
+                        }
                     }
                 }
 
index dd3f3da..bd6c07a 100644 (file)
@@ -84,7 +84,7 @@ namespace Internal.Reflection.Execution
                 {
                     if (foundMatch)
                     {
-                        exception = new AmbiguousMatchException();
+                        exception = new AmbiguousMatchException(SR.Format(SR.AmbiguousMatchException_Assembly, refName.FullName));
                         return false;
                     }
 
index 949e8ad..cddf33a 100644 (file)
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <root>
-  <!-- 
-    Microsoft ResX Schema 
-    
+  <!--
+    Microsoft ResX Schema
+
     Version 2.0
-    
-    The primary goals of this format is to allow a simple XML format 
-    that is mostly human readable. The generation and parsing of the 
-    various data types are done through the TypeConverter classes 
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
     associated with the data types.
-    
+
     Example:
-    
+
     ... ado.net/XML headers & schema ...
     <resheader name="resmimetype">text/microsoft-resx</resheader>
     <resheader name="version">2.0</resheader>
         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
         <comment>This is a comment</comment>
     </data>
-                
-    There are any number of "resheader" rows that contain simple 
+
+    There are any number of "resheader" rows that contain simple
     name/value pairs.
-    
-    Each data row contains a name, and value. The row also contains a 
-    type or mimetype. Type corresponds to a .NET class that support 
-    text/value conversion through the TypeConverter architecture. 
-    Classes that don't support this are serialized and stored with the 
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
     mimetype set.
-    
-    The mimetype is used for serialized objects, and tells the 
-    ResXResourceReader how to depersist the object. This is currently not 
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
     extensible. For a given mimetype the value must be set accordingly:
-    
-    Note - application/x-microsoft.net.object.binary.base64 is the format 
-    that the ResXResourceWriter will generate, however the reader can 
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
     read any of the formats listed below.
-    
+
     mimetype: application/x-microsoft.net.object.binary.base64
-    value   : The object must be serialized with 
+    value   : The object must be serialized with
             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
             : and then encoded with base64 encoding.
-    
+
     mimetype: application/x-microsoft.net.object.soap.base64
-    value   : The object must be serialized with 
+    value   : The object must be serialized with
             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
             : and then encoded with base64 encoding.
 
     mimetype: application/x-microsoft.net.object.bytearray.base64
-    value   : The object must be serialized into a byte array 
+    value   : The object must be serialized into a byte array
             : using a System.ComponentModel.TypeConverter
             : and then encoded with base64 encoding.
     -->
   <data name="FileLoadException_RefDefMismatch" xml:space="preserve">
     <value>Cannot load assembly '{0}'. The assembly exists but its version {1} is lower than the requested version {2}.</value>
   </data>
+  <data name="AmbiguousMatchException_Assembly" xml:space="preserve">
+    <value>Ambiguous match found for assembly '{0}'.</value>
+  </data>
 </root>
index 90536bd..9b6bb54 100644 (file)
   <data name="AppDomain_Policy_PrincipalTwice" xml:space="preserve">
     <value>Default principal object cannot be set twice.</value>
   </data>
-  <data name="AmbiguousImplementationException_NullMessage" xml:space="preserve">
-    <value>Ambiguous implementation found.</value>
-  </data>
   <data name="Arg_AccessException" xml:space="preserve">
     <value>Cannot access member.</value>
   </data>
   <data name="Arg_AccessViolationException" xml:space="preserve">
     <value>Attempted to read or write protected memory. This is often an indication that other memory is corrupt.</value>
   </data>
-  <data name="Arg_AmbiguousMatchException" xml:space="preserve">
+  <data name="Arg_AmbiguousImplementationException_NoMessage" xml:space="preserve">
+    <value>Ambiguous implementation found.</value>
+  </data>
+  <data name="Arg_AmbiguousMatchException_Attribute" xml:space="preserve">
+    <value>Multiple custom attributes of the same type '{0}' found.</value>
+  </data>
+  <data name="Arg_AmbiguousMatchException_NoMessage" xml:space="preserve">
     <value>Ambiguous match found.</value>
   </data>
+  <data name="Arg_AmbiguousMatchException_CustomAttributeData" xml:space="preserve">
+    <value>Ambiguous match found for '{0}'.</value>
+  </data>
+  <data name="Arg_AmbiguousMatchException_MemberInfo" xml:space="preserve">
+    <value>Ambiguous match found for '{0} {1}'.</value>
+  </data>
   <data name="Arg_ApplicationException" xml:space="preserve">
     <value>Error in the application.</value>
   </data>
   <data name="Resources_StreamNotValid" xml:space="preserve">
     <value>Stream is not a valid resource file.</value>
   </data>
-  <data name="RFLCT_AmbigCust" xml:space="preserve">
-    <value>Multiple custom attributes of the same type found.</value>
-  </data>
   <data name="InvalidFilterCriteriaException_CritInt" xml:space="preserve">
     <value>An Int32 must be provided for the filter criteria.</value>
   </data>
index 1a2e483..44c1449 100644 (file)
@@ -383,8 +383,10 @@ namespace System
 #endregion
             }
 
+            MethodBase bestMatch = candidates[currentMin]!;
+
             if (ambig)
-                throw new AmbiguousMatchException();
+                throw ThrowHelper.GetAmbiguousMatchException(bestMatch);
 
             // Reorder (if needed)
             if (names != null)
@@ -395,7 +397,7 @@ namespace System
 
             // If the parameters and the args are not the same length or there is a paramArray
             //  then we need to create a argument array.
-            ParameterInfo[] parameters = candidates[currentMin]!.GetParametersNoCopy();
+            ParameterInfo[] parameters = bestMatch.GetParametersNoCopy();
             if (parameters.Length == args.Length)
             {
                 if (paramArrayTypes[currentMin] != null)
@@ -431,7 +433,7 @@ namespace System
             }
             else
             {
-                if ((candidates[currentMin]!.CallingConvention & CallingConventions.VarArgs) == 0)
+                if ((bestMatch.CallingConvention & CallingConventions.VarArgs) == 0)
                 {
                     object[] objs = new object[parameters.Length];
                     int paramArrayPos = parameters.Length - 1;
@@ -442,7 +444,7 @@ namespace System
                 }
             }
 
-            return candidates[currentMin]!;
+            return bestMatch;
         }
 
         // Given a set of fields that match the base criteria, select a field.
@@ -526,9 +528,10 @@ namespace System
                     }
                 }
             }
+            FieldInfo bestMatch = candidates[currentMin];
             if (ambig)
-                throw new AmbiguousMatchException();
-            return candidates[currentMin];
+                throw ThrowHelper.GetAmbiguousMatchException(bestMatch);
+            return bestMatch;
         }
 
         // Given a set of methods that match the base criteria, select a method based
@@ -620,9 +623,10 @@ namespace System
                     }
                 }
             }
+            MethodBase bestMatch = candidates[currentMin];
             if (ambig)
-                throw new AmbiguousMatchException();
-            return candidates[currentMin];
+                throw ThrowHelper.GetAmbiguousMatchException(bestMatch);
+            return bestMatch;
         }
 
         // Given a set of properties that match the base criteria, select one.
@@ -734,10 +738,10 @@ namespace System
                     currentMin = i;
                 }
             }
-
+            PropertyInfo bestMatch = candidates[currentMin];
             if (ambig)
-                throw new AmbiguousMatchException();
-            return candidates[currentMin];
+                throw ThrowHelper.GetAmbiguousMatchException(bestMatch);
+            return bestMatch;
         }
 
         // ChangeType
@@ -850,7 +854,7 @@ namespace System
                     continue;
 
                 if (bestMatch != null)
-                    throw new AmbiguousMatchException();
+                    throw ThrowHelper.GetAmbiguousMatchException(bestMatch);
 
                 bestMatch = match[i];
             }
@@ -1137,7 +1141,7 @@ namespace System
                 // This can only happen if at least one is vararg or generic.
                 if (currentHierarchyDepth == deepestHierarchy)
                 {
-                    throw new AmbiguousMatchException();
+                    throw ThrowHelper.GetAmbiguousMatchException(methWithDeepestHierarchy!);
                 }
 
                 // Check to see if this method is on the most derived class.
index e86467d..b484a1d 100644 (file)
@@ -10,7 +10,7 @@ namespace System.Reflection
     public sealed class AmbiguousMatchException : SystemException
     {
         public AmbiguousMatchException()
-            : base(SR.Arg_AmbiguousMatchException)
+            : base(SR.Arg_AmbiguousMatchException_NoMessage)
         {
             HResult = HResults.COR_E_AMBIGUOUSMATCH;
         }
index 692a268..05665e0 100644 (file)
@@ -10,7 +10,7 @@ namespace System.Runtime
     public sealed class AmbiguousImplementationException : Exception
     {
         public AmbiguousImplementationException()
-            : base(SR.AmbiguousImplementationException_NullMessage)
+            : base(SR.Arg_AmbiguousImplementationException_NoMessage)
         {
             HResult = HResults.COR_E_AMBIGUOUSIMPLEMENTATION;
         }
index 3f4ae02..21281e4 100644 (file)
@@ -40,6 +40,8 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Numerics;
+using System.Reflection;
+using System.Runtime;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.Intrinsics;
@@ -605,6 +607,22 @@ namespace System
             throw new FormatException(SR.Format_IndexOutOfRange);
         }
 
+        internal static AmbiguousMatchException GetAmbiguousMatchException(MemberInfo memberInfo)
+        {
+            Type? declaringType = memberInfo.DeclaringType;
+            return new AmbiguousMatchException(SR.Format(SR.Arg_AmbiguousMatchException_MemberInfo, declaringType, memberInfo));
+        }
+
+        internal static AmbiguousMatchException GetAmbiguousMatchException(Attribute attribute)
+        {
+            return new AmbiguousMatchException(SR.Format(SR.Arg_AmbiguousMatchException_Attribute, attribute));
+        }
+
+        internal static AmbiguousMatchException GetAmbiguousMatchException(CustomAttributeData customAttributeData)
+        {
+            return new AmbiguousMatchException(SR.Format(SR.Arg_AmbiguousMatchException_CustomAttributeData, customAttributeData));
+        }
+
         private static Exception GetArraySegmentCtorValidationFailedException(Array? array, int offset, int count)
         {
             if (array == null)
index 3b0d6ec..275355e 100644 (file)
   <data name="Argument_ObjectArgumentMismatch" xml:space="preserve">
     <value>Object of type '{0}' cannot be converted to type '{1}'.</value>
   </data>
-</root>
\ No newline at end of file
+  <data name="Arg_AmbiguousMatchException_MemberInfo" xml:space="preserve">
+    <value>Ambiguous match found for '{0} {1}'.</value>
+  </data>
+</root>
index 543e113..95ee34a 100644 (file)
@@ -257,7 +257,6 @@ namespace System.Reflection.Context.Custom
                 }
             }
 
-
             if (matchingMethods.Count == 0)
                 return null;
 
@@ -266,10 +265,12 @@ namespace System.Reflection.Context.Custom
                 Debug.Assert(types == null || types.Length == 0);
 
                 // matches any signature
+                MethodInfo match = matchingMethods[0];
                 if (matchingMethods.Count == 1)
-                    return matchingMethods[0];
-                else
-                    throw new AmbiguousMatchException();
+                    return match;
+
+                Type? declaringType = match.DeclaringType;
+                throw new AmbiguousMatchException(SR.Format(SR.Arg_AmbiguousMatchException_MemberInfo, declaringType, match));
             }
             else
             {
index 4412920..2f48f0b 100644 (file)
   <data name="Arg_InvalidPath" xml:space="preserve">
     <value>The path '{0}' is not valid.</value>
   </data>
-</root>
\ No newline at end of file
+  <data name="Arg_AmbiguousMatchException_RoDefinitionType" xml:space="preserve">
+    <value>Ambiguous match found for '{0}'.</value>
+  </data>
+  <data name="Arg_AmbiguousMatchException_MemberInfo" xml:space="preserve">
+    <value>Ambiguous match found for '{0} {1}'.</value>
+  </data>
+</root>
index fb84ae9..76d07dd 100644 (file)
     <Compile Include="System\Reflection\TypeLoading\Types\RoType.TypeClassification.cs" />
     <Compile Include="System\Reflection\TypeLoading\Types\RoWrappedType.cs" />
     <Compile Include="$(CommonPath)System\Obsoletions.cs" Link="Common\System\Obsoletions.cs" />
+    <Compile Include="System\ThrowHelper.cs" />
   </ItemGroup>
 
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
index e743841..10501e1 100644 (file)
@@ -137,9 +137,10 @@ namespace System
                     ambig = false;
                 }
             }
+            MethodBase bestMatch = candidates[currentMin];
             if (ambig)
-                throw new AmbiguousMatchException();
-            return candidates[currentMin];
+                throw ThrowHelper.GetAmbiguousMatchException(bestMatch);
+            return bestMatch;
         }
 
         // Given a set of properties that match the base criteria, select one.
@@ -256,9 +257,10 @@ namespace System
                 }
             }
 
+            PropertyInfo bestMatch = candidates[currentMin];
             if (ambig)
-                throw new AmbiguousMatchException();
-            return candidates[currentMin];
+                throw ThrowHelper.GetAmbiguousMatchException(bestMatch);
+            return bestMatch;
         }
 
         // The default binder doesn't support any change type functionality.
@@ -341,7 +343,7 @@ namespace System
                     continue;
 
                 if (bestMatch != null)
-                    throw new AmbiguousMatchException();
+                    throw ThrowHelper.GetAmbiguousMatchException(bestMatch);
 
                 bestMatch = match[i];
             }
@@ -606,7 +608,7 @@ namespace System
                 // This can only happen if at least one is vararg or generic.
                 if (currentHierarchyDepth == deepestHierarchy)
                 {
-                    throw new AmbiguousMatchException();
+                    throw ThrowHelper.GetAmbiguousMatchException(methWithDeepestHierarchy!);
                 }
 
                 // Check to see if this method is on the most derived class.
index f90ff00..74ace1c 100644 (file)
@@ -112,11 +112,11 @@ namespace System.Reflection.Runtime.BindingFlagSupport
                         // declared by the most derived type. Since QueriedMemberLists are sorted in order of decreasing derivation,
                         // that means we let the first match win - unless, of course, they're both the "most derived member".
                         if (match.DeclaringType!.Equals(challenger.DeclaringType))
-                            throw new AmbiguousMatchException();
+                            throw ThrowHelper.GetAmbiguousMatchException(match);
 
                         MemberPolicies<M> policies = MemberPolicies<M>.Default;
                         if (!policies.OkToIgnoreAmbiguity(match, challenger))
-                            throw new AmbiguousMatchException();
+                            throw ThrowHelper.GetAmbiguousMatchException(match);
                     }
                     else
                     {
index 56d7bfb..745b5d1 100644 (file)
@@ -17,10 +17,10 @@ namespace System.Reflection.TypeLoading
             MemberInfo[] members = attributeType.GetMember(name, MemberTypes.Field | MemberTypes.Property, BindingFlags.Public | BindingFlags.Instance);
             if (members.Length == 0)
                 throw new MissingMemberException(attributeType.FullName, name);
+            MemberInfo match = members[0];
             if (members.Length > 1)
-                throw new AmbiguousMatchException();
-
-            return new CustomAttributeNamedArgument(members[0], new CustomAttributeTypedArgument(argumentType!, value));
+                throw ThrowHelper.GetAmbiguousMatchException(match);
+            return new CustomAttributeNamedArgument(match, new CustomAttributeTypedArgument(argumentType!, value));
         }
 
         /// <summary>
index 247379c..ed9d800 100644 (file)
@@ -136,10 +136,11 @@ namespace System.Reflection.TypeLoading
                 // For perf and .NET Framework compat, fast-path these specific checks before calling on the binder to break ties.
                 if (types == null || types.Length == 0)
                 {
+                    PropertyInfo firstCandidate = candidates[0];
+
                     // no arguments
                     if (candidates.Count == 1)
                     {
-                        PropertyInfo firstCandidate = candidates[0];
                         if (!(returnType is null) && !returnType.IsEquivalentTo(firstCandidate.PropertyType))
                             return null;
                         return firstCandidate;
@@ -148,7 +149,7 @@ namespace System.Reflection.TypeLoading
                     {
                         if (returnType is null)
                             // if we are here we have no args or property type to select over and we have more than one property with that name
-                            throw new AmbiguousMatchException();
+                            throw ThrowHelper.GetAmbiguousMatchException(firstCandidate);
                     }
                 }
 
index 099c52d..cf19419 100644 (file)
@@ -94,7 +94,7 @@ namespace System.Reflection.TypeLoading.Ecma
                 if (nestedTypeDefinition.Name.Equals(utf8Name, reader))
                 {
                     if (match != null)
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(match);
                     match = handle.ResolveTypeDef(GetEcmaModule());
                 }
             }
index 60530bb..e9d9881 100644 (file)
@@ -33,7 +33,7 @@ namespace System.Reflection.TypeLoading
                 if (ns.Length != 0 && !ns.Equals(ifc.Namespace))
                     continue;
                 if (match != null)
-                    throw new AmbiguousMatchException();
+                    throw ThrowHelper.GetAmbiguousMatchException(match);
                 match = ifc;
             }
             return match;
diff --git a/src/libraries/System.Reflection.MetadataLoadContext/src/System/ThrowHelper.cs b/src/libraries/System.Reflection.MetadataLoadContext/src/System/ThrowHelper.cs
new file mode 100644 (file)
index 0000000..0b28c0c
--- /dev/null
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// This file defines an internal static class used to throw exceptions in the
+// the System.Reflection.MetadataLoadContext code.
+
+using System.Reflection;
+using System.Reflection.TypeLoading;
+
+namespace System
+{
+    internal static class ThrowHelper
+    {
+        internal static AmbiguousMatchException GetAmbiguousMatchException(RoDefinitionType roDefinitionType)
+        {
+            return new AmbiguousMatchException(SR.Format(SR.Arg_AmbiguousMatchException_RoDefinitionType, roDefinitionType.FullName));
+        }
+
+        internal static AmbiguousMatchException GetAmbiguousMatchException(MemberInfo memberInfo)
+        {
+            Type? declaringType = memberInfo.DeclaringType;
+            return  new AmbiguousMatchException(SR.Format(SR.Arg_AmbiguousMatchException_MemberInfo, declaringType, memberInfo));
+        }
+    }
+}
index 08d7e04..828dc3e 100644 (file)
@@ -70,7 +70,7 @@
     <value>Event invocation for COM objects requires the declaring interface of the event to be attributed with ComEventInterfaceAttribute.</value>
   </data>
   <data name="AmbiguousMatch_MultipleEventInterfaceAttributes" xml:space="preserve">
-    <value>More than one ComEventInterfaceAttribute was found on the declaring interface of the event.</value>
+    <value>More than one ComEventInterfaceAttribute found for '{0}' on the declaring interface of the event.</value>
   </data>
   <data name="InvalidOperation_NoDispIdAttribute" xml:space="preserve">
     <value>Event invocation for COM objects requires event to be attributed with DispIdAttribute.</value>
index 50fbb3b..2ecc3eb 100644 (file)
@@ -106,14 +106,15 @@ namespace System.Runtime.InteropServices
                 throw new InvalidOperationException(SR.InvalidOperation_NoComEventInterfaceAttribute);
             }
 
+            ComEventInterfaceAttribute interfaceAttribute = (ComEventInterfaceAttribute)comEventInterfaces[0];
+
             if (comEventInterfaces.Length > 1)
             {
-                throw new AmbiguousMatchException(SR.AmbiguousMatch_MultipleEventInterfaceAttributes);
+                throw new AmbiguousMatchException(SR.Format(SR.AmbiguousMatch_MultipleEventInterfaceAttributes, interfaceAttribute));
             }
 
-            Type sourceInterface = ((ComEventInterfaceAttribute)comEventInterfaces[0]).SourceInterface;
+            Type sourceInterface = interfaceAttribute.SourceInterface;
             Guid guid = sourceInterface.GUID;
-
             MethodInfo methodInfo = sourceInterface.GetMethod(eventInfo.Name)!;
             Attribute? dispIdAttribute = Attribute.GetCustomAttribute(methodInfo, typeof(DispIdAttribute));
             if (dispIdAttribute == null)
index 7cdec76..f885a35 100644 (file)
@@ -17,9 +17,10 @@ namespace System
             object[] attrs = CustomAttribute.GetCustomAttributes(element, attributeType, inherit);
             if (attrs == null || attrs.Length == 0)
                 return null;
+            Attribute match = (Attribute)attrs[0];
             if (attrs.Length != 1)
-                throw new AmbiguousMatchException();
-            return (Attribute)(attrs[0]);
+                throw ThrowHelper.GetAmbiguousMatchException(match);
+            return match;
         }
 
         public static Attribute? GetCustomAttribute(Assembly element, Type attributeType) => GetAttr(element, attributeType, true);
index 8f4729a..cb8beb6 100644 (file)
@@ -431,7 +431,7 @@ namespace System.Reflection.Emit
                 if (types == null)
                 {
                     if (count > 1)
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(found!);
                     return found;
                 }
                 MethodBase[] match = new MethodBase[count];
index 014aa96..e34b378 100644 (file)
@@ -794,7 +794,7 @@ namespace System
                     {
                         MethodInfo methodInfo = candidates[j];
                         if (!System.DefaultBinder.CompareMethodSig(methodInfo, firstCandidate))
-                            throw new AmbiguousMatchException();
+                            throw ThrowHelper.GetAmbiguousMatchException(firstCandidate);
                     }
 
                     // All the methods have the exact same name and sig so return the most derived one.
@@ -850,10 +850,10 @@ namespace System
             if (types == null || types.Length == 0)
             {
                 // no arguments
+                PropertyInfo firstCandidate = candidates[0];
+
                 if (candidates.Count == 1)
                 {
-                    PropertyInfo firstCandidate = candidates[0];
-
                     if (returnType is not null && !returnType.IsEquivalentTo(firstCandidate.PropertyType))
                         return null;
 
@@ -863,7 +863,7 @@ namespace System
                 {
                     if (returnType is null)
                         // if we are here we have no args or property type to select over and we have more than one property with that name
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(firstCandidate);
                 }
             }
 
@@ -894,7 +894,7 @@ namespace System
                 if ((bindingAttr & eventInfo.BindingFlags) == eventInfo.BindingFlags)
                 {
                     if (match != null)
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(match);
 
                     match = eventInfo;
                 }
@@ -923,7 +923,7 @@ namespace System
                     if (match != null)
                     {
                         if (ReferenceEquals(fieldInfo.DeclaringType, match.DeclaringType))
-                            throw new AmbiguousMatchException();
+                            throw ThrowHelper.GetAmbiguousMatchException(match);
 
                         if (match.DeclaringType!.IsInterface && fieldInfo.DeclaringType!.IsInterface)
                             multipleStaticFieldMatches = true;
@@ -935,7 +935,7 @@ namespace System
             }
 
             if (multipleStaticFieldMatches && match!.DeclaringType!.IsInterface)
-                throw new AmbiguousMatchException();
+                throw ThrowHelper.GetAmbiguousMatchException(match);
 
             return match;
         }
@@ -987,7 +987,7 @@ namespace System
                 if (FilterApplyType(iface, bindingAttr, name, false, ns))
                 {
                     if (match != null)
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(match);
 
                     match = iface;
                 }
@@ -1015,7 +1015,7 @@ namespace System
                 if (FilterApplyType(nestedType, bindingAttr, name, false, ns))
                 {
                     if (match != null)
-                        throw new AmbiguousMatchException();
+                        throw ThrowHelper.GetAmbiguousMatchException(match);
 
                     match = nestedType;
                 }