1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Diagnostics; 7 using System.Linq; 8 using System.Linq.Expressions; 9 using System.Reflection; 10 11 namespace Microsoft.CSharp.RuntimeBinder 12 { 13 internal static class RuntimeBinderExtensions 14 { IsNullableType(this Type type)15 public static bool IsNullableType(this Type type) 16 { 17 return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); 18 } 19 20 // This method is intended as a means to detect when MemberInfos are the same, 21 // modulo the fact that they can appear to have different but equivalent local 22 // No-PIA types. It is by the symbol table to determine whether 23 // or not members have been added to an AggSym or not. IsEquivalentTo(this MemberInfo mi1, MemberInfo mi2)24 public static bool IsEquivalentTo(this MemberInfo mi1, MemberInfo mi2) 25 { 26 if (mi1 == null || mi2 == null) 27 { 28 return mi1 == null && mi2 == null; 29 } 30 31 #if UNSUPPORTEDAPI 32 if (mi1 == mi2 || (mi1.DeclaringType.IsGenericallyEqual(mi2.DeclaringType) && mi1.MetadataToken == mi2.MetadataToken)) 33 #else 34 if (mi1.Equals(mi2)) 35 #endif 36 { 37 return true; 38 } 39 40 if (mi1 is MethodInfo method1) 41 { 42 if (!(mi2 is MethodInfo method2) || method1.IsGenericMethod != method2.IsGenericMethod) 43 { 44 return false; 45 } 46 47 if (method1.IsGenericMethod) 48 { 49 method1 = method1.GetGenericMethodDefinition(); 50 method2 = method2.GetGenericMethodDefinition(); 51 52 if (method1.GetGenericArguments().Length != method2.GetGenericArguments().Length) 53 { 54 return false; // Methods of different arity are not equivalent. 55 } 56 } 57 58 return method1 != method2 59 && method1.CallingConvention == method2.CallingConvention 60 && method1.Name == method2.Name 61 && method1.DeclaringType.IsGenericallyEqual(method2.DeclaringType) 62 && method1.ReturnType.IsGenericallyEquivalentTo(method2.ReturnType, method1, method2) 63 && method1.AreParametersEquivalent(method2); 64 } 65 66 if (mi1 is ConstructorInfo ctor1) 67 { 68 return mi2 is ConstructorInfo ctor2 69 && ctor1 != ctor2 70 && ctor1.CallingConvention == ctor2.CallingConvention 71 && ctor1.DeclaringType.IsGenericallyEqual(ctor2.DeclaringType) 72 && ctor1.AreParametersEquivalent(ctor2); 73 } 74 75 return mi1 is PropertyInfo prop1 && mi2 is PropertyInfo prop2 76 && prop1 != prop2 77 && prop1.Name == prop2.Name 78 && prop1.DeclaringType.IsGenericallyEqual(prop2.DeclaringType) 79 && prop1.PropertyType.IsGenericallyEquivalentTo(prop2.PropertyType, prop1, prop2) 80 && prop1.GetGetMethod(true).IsEquivalentTo(prop2.GetGetMethod(true)) 81 && prop1.GetSetMethod(true).IsEquivalentTo(prop2.GetSetMethod(true)); 82 } 83 AreParametersEquivalent(this MethodBase method1, MethodBase method2)84 private static bool AreParametersEquivalent(this MethodBase method1, MethodBase method2) 85 { 86 ParameterInfo[] pis1 = method1.GetParameters(); 87 ParameterInfo[] pis2 = method2.GetParameters(); 88 if (pis1.Length != pis2.Length) 89 { 90 return false; 91 } 92 93 for (int i = 0; i < pis1.Length; ++i) 94 { 95 if (!pis1[i].IsEquivalentTo(pis2[i], method1, method2)) 96 { 97 return false; 98 } 99 } 100 101 return true; 102 } 103 IsEquivalentTo(this ParameterInfo pi1, ParameterInfo pi2, MethodBase method1, MethodBase method2)104 private static bool IsEquivalentTo(this ParameterInfo pi1, ParameterInfo pi2, MethodBase method1, MethodBase method2) 105 { 106 if (pi1 == null || pi2 == null) 107 { 108 return pi1 == null && pi2 == null; 109 } 110 111 if (pi1.Equals(pi2)) 112 { 113 return true; 114 } 115 116 return pi1.ParameterType.IsGenericallyEquivalentTo(pi2.ParameterType, method1, method2); 117 } 118 IsGenericallyEqual(this Type t1, Type t2)119 private static bool IsGenericallyEqual(this Type t1, Type t2) 120 { 121 if (t1 == null || t2 == null) 122 { 123 return t1 == null && t2 == null; 124 } 125 126 if (t1.Equals(t2)) 127 { 128 return true; 129 } 130 131 if (t1.IsConstructedGenericType || t2.IsConstructedGenericType) 132 { 133 Type t1def = t1.IsConstructedGenericType ? t1.GetGenericTypeDefinition() : t1; 134 Type t2def = t2.IsConstructedGenericType ? t2.GetGenericTypeDefinition() : t2; 135 136 return t1def.Equals(t2def); 137 } 138 139 return false; 140 } 141 142 // Compares two types and calls them equivalent if a type parameter equals a type argument. 143 // i.e if the inputs are (T, int, C<T>, C<int>) then this will return true. IsGenericallyEquivalentTo(this Type t1, Type t2, MemberInfo member1, MemberInfo member2)144 private static bool IsGenericallyEquivalentTo(this Type t1, Type t2, MemberInfo member1, MemberInfo member2) 145 { 146 Debug.Assert(!(member1 is MethodBase) || 147 !((MethodBase)member1).IsGenericMethod || 148 (((MethodBase)member1).IsGenericMethodDefinition && ((MethodBase)member2).IsGenericMethodDefinition)); 149 150 if (t1.Equals(t2)) 151 { 152 return true; 153 } 154 155 // If one of them is a type param and then the other is a real type, then get the type argument in the member 156 // or it's declaring type that corresponds to the type param and compare that to the other type. 157 if (t1.IsGenericParameter) 158 { 159 if (t2.IsGenericParameter) 160 { 161 // If member's declaring type is not type parameter's declaring type, we assume that it is used as a type argument 162 if (t1.DeclaringMethod == null && member1.DeclaringType.Equals(t1.DeclaringType)) 163 { 164 if (!(t2.DeclaringMethod == null && member2.DeclaringType.Equals(t2.DeclaringType))) 165 { 166 return t1.IsTypeParameterEquivalentToTypeInst(t2, member2); 167 } 168 } 169 else if (t2.DeclaringMethod == null && member2.DeclaringType.Equals(t2.DeclaringType)) 170 { 171 return t2.IsTypeParameterEquivalentToTypeInst(t1, member1); 172 } 173 174 // If both of these are type params but didn't compare to be equal then one of them is likely bound to another 175 // open type. Simply disallow such cases. 176 return false; 177 } 178 179 return t1.IsTypeParameterEquivalentToTypeInst(t2, member2); 180 } 181 else if (t2.IsGenericParameter) 182 { 183 return t2.IsTypeParameterEquivalentToTypeInst(t1, member1); 184 } 185 186 // Recurse in for generic types arrays, byref and pointer types. 187 if (t1.IsGenericType && t2.IsGenericType) 188 { 189 var args1 = t1.GetGenericArguments(); 190 var args2 = t2.GetGenericArguments(); 191 192 if (args1.Length == args2.Length) 193 { 194 return t1.IsGenericallyEqual(t2) && 195 Enumerable.All(Enumerable.Zip(args1, args2, (ta1, ta2) => ta1.IsGenericallyEquivalentTo(ta2, member1, member2)), x => x); 196 } 197 } 198 199 if (t1.IsArray && t2.IsArray) 200 { 201 return t1.GetArrayRank() == t2.GetArrayRank() && 202 t1.GetElementType().IsGenericallyEquivalentTo(t2.GetElementType(), member1, member2); 203 } 204 205 if ((t1.IsByRef && t2.IsByRef) || 206 (t1.IsPointer && t2.IsPointer)) 207 { 208 return t1.GetElementType().IsGenericallyEquivalentTo(t2.GetElementType(), member1, member2); 209 } 210 211 return false; 212 } 213 IsTypeParameterEquivalentToTypeInst(this Type typeParam, Type typeInst, MemberInfo member)214 private static bool IsTypeParameterEquivalentToTypeInst(this Type typeParam, Type typeInst, MemberInfo member) 215 { 216 Debug.Assert(typeParam.IsGenericParameter); 217 218 if (typeParam.DeclaringMethod != null) 219 { 220 // The type param is from a generic method. Since only methods can be generic, anything else 221 // here means they are not equivalent. 222 if (!(member is MethodBase)) 223 { 224 return false; 225 } 226 227 MethodBase method = (MethodBase)member; 228 int position = typeParam.GenericParameterPosition; 229 Type[] args = method.IsGenericMethod ? method.GetGenericArguments() : null; 230 231 return args != null && 232 args.Length > position && 233 args[position].Equals(typeInst); 234 } 235 else 236 { 237 return member.DeclaringType.GetGenericArguments()[typeParam.GenericParameterPosition].Equals(typeInst); 238 } 239 } 240 241 // s_MemberEquivalence will replace itself with one version or another 242 // depending on what works at run time 243 private static Func<MemberInfo, MemberInfo, bool> s_MemberEquivalence = (m1, m2) => 244 { 245 try 246 { 247 Type memberInfo = typeof(MemberInfo); 248 249 // First, try the actual API. (Post .NetCore 2.0) The api is the only one that gets it completely right on frameworks without MetadataToken. 250 MethodInfo apiMethod = memberInfo.GetMethod( 251 "HasSameMetadataDefinitionAs", 252 BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.ExactBinding, 253 binder: null, 254 types: new Type[] { typeof(MemberInfo) }, 255 modifiers: null); 256 if (apiMethod != null) 257 { 258 Func<MemberInfo, MemberInfo, bool> apiDelegate = (Func<MemberInfo, MemberInfo, bool>)(apiMethod.CreateDelegate(typeof(Func<MemberInfo, MemberInfo, bool>))); 259 try 260 { 261 bool result = apiDelegate(m1, m2); 262 // it worked, so publish it 263 s_MemberEquivalence = apiDelegate; 264 return result; 265 } 266 catch 267 { 268 // Api found but apparently stubbed as not supported. Continue on to the next fallback... 269 } 270 } 271 272 // See if MetadataToken property is available. 273 PropertyInfo property = memberInfo.GetProperty("MetadataToken", typeof(int), Array.Empty<Type>()); 274 275 if ((object)property != null && property.CanRead) 276 { 277 // (parameter1, parameter2) => parameter1.MetadataToken == parameter2.MetadataToken 278 var parameter1 = Expression.Parameter(memberInfo); 279 var parameter2 = Expression.Parameter(memberInfo); 280 var memberEquivalence = Expression.Lambda<Func<MemberInfo, MemberInfo, bool>>( 281 Expression.Equal( 282 Expression.Property(parameter1, property), 283 Expression.Property(parameter2, property)), 284 parameter1, parameter2).Compile(); 285 286 var result = memberEquivalence(m1, m2); 287 // it worked, so publish it 288 s_MemberEquivalence = memberEquivalence; 289 290 return result; 291 } 292 } 293 catch 294 { 295 // Platform might not allow access to the property 296 } 297 298 // MetadataToken is not available in some contexts. Looks like this is one of those cases. 299 // fallback to "IsEquivalentTo" 300 Func<MemberInfo, MemberInfo, bool> fallbackMemberEquivalence = (m1param, m2param) => m1param.IsEquivalentTo(m2param); 301 302 // fallback must work 303 s_MemberEquivalence = fallbackMemberEquivalence; 304 return fallbackMemberEquivalence(m1, m2); 305 }; 306 HasSameMetadataDefinitionAs(this MemberInfo mi1, MemberInfo mi2)307 public static bool HasSameMetadataDefinitionAs(this MemberInfo mi1, MemberInfo mi2) 308 { 309 #if UNSUPPORTEDAPI 310 return (mi1.MetadataToken == mi2.MetadataToken) && (mi1.Module == mi2.Module)); 311 #else 312 return mi1.Module.Equals(mi2.Module) && s_MemberEquivalence(mi1, mi2); 313 #endif 314 } 315 GetIndexerName(this Type type)316 public static string GetIndexerName(this Type type) 317 { 318 Debug.Assert(type != null); 319 string name = GetTypeIndexerName(type); 320 if (name == null && type.IsInterface) 321 { 322 foreach (Type iface in type.GetInterfaces()) 323 { 324 name = GetTypeIndexerName(iface); 325 if (name != null) 326 { 327 break; 328 } 329 } 330 } 331 332 return name; 333 } 334 GetTypeIndexerName(Type type)335 private static string GetTypeIndexerName(Type type) 336 { 337 Debug.Assert(type != null); 338 string name = type.GetCustomAttribute<DefaultMemberAttribute>()?.MemberName; 339 if (name != null) 340 { 341 if (type.GetProperties( 342 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance) 343 .Any(p => p.Name == name && p.GetIndexParameters().Length != 0)) 344 { 345 return name; 346 } 347 } 348 349 return null; 350 } 351 } 352 } 353