Implement HasSameMetadataDefinitionAs() on CoreCLR (#11774)
authorAtsushi Kanamori <AtsushiKan@users.noreply.github.com>
Mon, 22 May 2017 04:52:23 +0000 (21:52 -0700)
committerJan Kotas <jkotas@microsoft.com>
Mon, 22 May 2017 04:52:23 +0000 (21:52 -0700)
This api was approved here:

  https://github.com/dotnet/corefx/issues/5884

and is a necessary step to fixing the System.Dynamic.Runtime.Tests
failure:

  https://github.com/dotnet/corefx/issues/19895

which is caused by Microsoft.CSharp trying to do the impossible
and emulate this api without GetMetadataToken() support.

This approach opts for the most straightforward and efficient
implementation without any special-casing for weird situations
(this is also what Microsoft.CSharp implements today as
well as what someone else trying to trampoline members
across generic instantaitions is like to do.)

This results in the following behavior for these
corner cases. With the possible exception of #3,
I think they are tolerable enough to accept and codify:

1. "other" implemented by an entirely different Reflection
   provider than "this".

   Behavior:
     returns false without invoking any methods on the
     "other" Member.

   To change it to throw an ArgumentException would
   mean extra cast checks against the 6 possible
   Runtime types (or having said RuntimeTypes implement
   a sentinel interface.)

   Given that HasSameMetadataDefinitionAs() is a
   "looser cousin of Equals()" and "Equals()"
   doesn't throw for objects from a different universe,
   this seems reasonable.

2. Arrays, ByRefs, Pointers and Types from GetTypeFromCLSID()

   Behavior:
     Arrays, ByRefs, Pointers all return token 0x0600000
     and so they'll return "true" wrt to each other (provided
     both types are implemented by the same provider.)

     CLSID types all return the typedef of __ComObject
     so they'll return "true" wrt to each other.

     The constructor exposed by CLSID types all return
     the typedef of some constructor on __ComObject so
     they'll return "true" wrt to each other.

   I do not think these are interesting cases that merit
   special handling. These types will never appear
   in an enumeration of the members of a type. (The
   fact that Reflection surfaces them in objects
   that are assignable to MemberInfo is a structural
   flaw in Reflection's object model.)

3. Synthesized constructors and methods on array types.

   Behavior:
     These methods all return 0x06000000 as a token
     so the constructors will all compare true wrt
     each other, and likewise with the methods.

   This is a bit crummy though it's not clear
   what the "right" policy should look like.
   I could be persuaded to throw NotSupported
   for these, to leave the possibility open
   for a better story later. On the other hand,
   I wouldn't demand it either.

src/mscorlib/shared/System/Reflection/MemberInfo.cs
src/mscorlib/src/System/Reflection/MemberInfo.Internal.cs
src/mscorlib/src/System/Reflection/RuntimeConstructorInfo.cs
src/mscorlib/src/System/Reflection/RuntimeEventInfo.cs
src/mscorlib/src/System/Reflection/RuntimeFieldInfo.cs
src/mscorlib/src/System/Reflection/RuntimeMethodInfo.cs
src/mscorlib/src/System/Reflection/RuntimePropertyInfo.cs
src/mscorlib/src/System/RtType.cs

index 1275cc1..d8a7458 100644 (file)
@@ -30,6 +30,8 @@ namespace System.Reflection
             }
         }
 
+        public virtual bool HasSameMetadataDefinitionAs(MemberInfo other) { throw NotImplemented.ByDesign; }
+
         public abstract bool IsDefined(Type attributeType, bool inherit);
         public abstract object[] GetCustomAttributes(bool inherit);
         public abstract object[] GetCustomAttributes(Type attributeType, bool inherit);
index 8e7be56..9bb7b57 100644 (file)
@@ -7,5 +7,23 @@ namespace System.Reflection
     public abstract partial class MemberInfo
     {
         internal virtual bool CacheEquals(object o) { throw new NotImplementedException(); }
+
+        internal bool HasSameMetadataDefinitionAsCore<TOther>(MemberInfo other) where TOther : MemberInfo
+        {
+            if (other == null)
+                throw new ArgumentNullException(nameof(other));
+
+            // Ensure that "other" is a runtime-implemented MemberInfo. Do this check before calling any methods on it!
+            if (!(other is TOther)) 
+                return false;
+
+            if (MetadataToken != other.MetadataToken)
+                return false;
+
+            if (!(Module.Equals(other.Module)))
+                return false;
+
+            return true;
+        }
     }
 }
index 8c3b1fc..9693081 100644 (file)
@@ -218,6 +218,8 @@ namespace System.Reflection
             }
         }
 
+        public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimeConstructorInfo>(other);
+
         public override Type ReflectedType
         {
             get
index 930e182..88f1aff 100644 (file)
@@ -135,6 +135,7 @@ namespace System.Reflection
             }
         }
         public override Type DeclaringType { get { return m_declaringType; } }
+        public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimeEventInfo>(other);
         public override Type ReflectedType
         {
             get
index 29cc97d..b3dceaa 100644 (file)
@@ -68,6 +68,8 @@ namespace System.Reflection
             }
         }
 
+        public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimeFieldInfo>(other);
+
         public override Module Module { get { return GetRuntimeModule(); } }
         #endregion
 
index b8a2341..9a7da4d 100644 (file)
@@ -335,6 +335,8 @@ namespace System.Reflection
             }
         }
 
+        public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimeMethodInfo>(other);
+
         public override Type ReflectedType
         {
             get
index b6a4792..d684e6c 100644 (file)
@@ -207,6 +207,8 @@ namespace System.Reflection
             }
         }
 
+        public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimePropertyInfo>(other);
+
         public override Type ReflectedType
         {
             get
index e49894f..4bf78b2 100644 (file)
@@ -3789,6 +3789,8 @@ namespace System
         #endregion
 
         #region Misc
+        public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimeType>(other);
+
         public override bool IsTypeDefinition
         {
             get { return RuntimeTypeHandle.IsTypeDefinition(this); }