A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Reflection; 5using System.Runtime.CompilerServices; 6using UnityObject = UnityEngine.Object; 7 8namespace Unity.VisualScripting 9{ 10 public static class MemberUtility 11 { 12 static MemberUtility() 13 { 14 ExtensionMethodsCache = new Lazy<ExtensionMethodCache>(() => new ExtensionMethodCache(), true); 15 InheritedExtensionMethodsCache = new Lazy<Dictionary<Type, MethodInfo[]>>(() => new Dictionary<Type, MethodInfo[]>(), true); 16 GenericExtensionMethods = new Lazy<HashSet<MethodInfo>>(() => new HashSet<MethodInfo>(), true); 17 } 18 19 // The process of resolving generic methods is very expensive. 20 // Cache the results for each this parameter type. 21 private static readonly Lazy<ExtensionMethodCache> ExtensionMethodsCache; 22 private static readonly Lazy<Dictionary<Type, MethodInfo[]>> InheritedExtensionMethodsCache; 23 private static readonly Lazy<HashSet<MethodInfo>> GenericExtensionMethods; 24 25 public static bool IsOperator(this MethodInfo method) 26 { 27 return method.IsSpecialName && OperatorUtility.operatorNames.ContainsKey(method.Name); 28 } 29 30 public static bool IsUserDefinedConversion(this MethodInfo method) 31 { 32 return method.IsSpecialName && (method.Name == "op_Implicit" || method.Name == "op_Explicit"); 33 } 34 35 /// <remarks>This may return an open-constructed method as well.</remarks> 36 public static MethodInfo MakeGenericMethodVia(this MethodInfo openConstructedMethod, params Type[] closedConstructedParameterTypes) 37 { 38 Ensure.That(nameof(openConstructedMethod)).IsNotNull(openConstructedMethod); 39 Ensure.That(nameof(closedConstructedParameterTypes)).IsNotNull(closedConstructedParameterTypes); 40 41 if (!openConstructedMethod.ContainsGenericParameters) 42 { 43 // The method contains no generic parameters, 44 // it is by definition already resolved. 45 return openConstructedMethod; 46 } 47 48 var openConstructedParameterTypes = openConstructedMethod.GetParameters().Select(p => p.ParameterType).ToArray(); 49 50 if (openConstructedParameterTypes.Length != closedConstructedParameterTypes.Length) 51 { 52 throw new ArgumentOutOfRangeException(nameof(closedConstructedParameterTypes)); 53 } 54 55 var resolvedGenericParameters = new Dictionary<Type, Type>(); 56 57 for (var i = 0; i < openConstructedParameterTypes.Length; i++) 58 { 59 // Resolve each open-constructed parameter type via the equivalent 60 // closed-constructed parameter type. 61 62 var openConstructedParameterType = openConstructedParameterTypes[i]; 63 var closedConstructedParameterType = closedConstructedParameterTypes[i]; 64 65 openConstructedParameterType.MakeGenericTypeVia(closedConstructedParameterType, resolvedGenericParameters); 66 } 67 68 // Construct the final closed-constructed method from the resolved arguments 69 70 var openConstructedGenericArguments = openConstructedMethod.GetGenericArguments(); 71 var closedConstructedGenericArguments = openConstructedGenericArguments.Select(openConstructedGenericArgument => 72 { 73 // If the generic argument has been successfully resolved, use it; 74 // otherwise, leave the open-constructed argument in place. 75 76 if (resolvedGenericParameters.ContainsKey(openConstructedGenericArgument)) 77 { 78 return resolvedGenericParameters[openConstructedGenericArgument]; 79 } 80 else 81 { 82 return openConstructedGenericArgument; 83 } 84 }).ToArray(); 85 86 return openConstructedMethod.MakeGenericMethod(closedConstructedGenericArguments); 87 } 88 89 public static bool IsGenericExtension(this MethodInfo methodInfo) 90 { 91 return GenericExtensionMethods.Value.Contains(methodInfo); 92 } 93 94 private static IEnumerable<MethodInfo> GetInheritedExtensionMethods(Type thisArgumentType) 95 { 96 var methodInfos = ExtensionMethodsCache.Value.Cache; 97 foreach (var extensionMethod in methodInfos) 98 { 99 var compatibleThis = extensionMethod.GetParameters()[0].ParameterType.CanMakeGenericTypeVia(thisArgumentType); 100 101 if (compatibleThis) 102 { 103 if (extensionMethod.ContainsGenericParameters) 104 { 105 var closedConstructedParameterTypes = thisArgumentType.Yield().Concat(extensionMethod.GetParametersWithoutThis().Select(p => p.ParameterType)); 106 107 var closedConstructedMethod = extensionMethod.MakeGenericMethodVia(closedConstructedParameterTypes.ToArray()); 108 109 GenericExtensionMethods.Value.Add(closedConstructedMethod); 110 111 yield return closedConstructedMethod; 112 } 113 else 114 { 115 yield return extensionMethod; 116 } 117 } 118 } 119 } 120 121 public static IEnumerable<MethodInfo> GetExtensionMethods(this Type thisArgumentType, bool inherited = true) 122 { 123 if (inherited) 124 { 125 lock (InheritedExtensionMethodsCache) 126 { 127 if (!InheritedExtensionMethodsCache.Value.TryGetValue(thisArgumentType, out var inheritedExtensionMethods)) 128 { 129 inheritedExtensionMethods = GetInheritedExtensionMethods(thisArgumentType).ToArray(); 130 InheritedExtensionMethodsCache.Value.Add(thisArgumentType, inheritedExtensionMethods); 131 } 132 133 return inheritedExtensionMethods; 134 } 135 } 136 else 137 { 138 var methodInfos = ExtensionMethodsCache.Value.Cache; 139 return methodInfos.Where(method => method.GetParameters()[0].ParameterType == thisArgumentType); 140 } 141 } 142 143 public static bool IsExtension(this MethodInfo methodInfo) 144 { 145 return methodInfo.HasAttribute<ExtensionAttribute>(false); 146 } 147 148 public static bool IsExtensionMethod(this MemberInfo memberInfo) 149 { 150 return memberInfo is MethodInfo methodInfo && methodInfo.IsExtension(); 151 } 152 153 public static Delegate CreateDelegate(this MethodInfo methodInfo, Type delegateType) 154 { 155 return Delegate.CreateDelegate(delegateType, methodInfo); 156 } 157 158 public static bool IsAccessor(this MemberInfo memberInfo) 159 { 160 return memberInfo is FieldInfo || memberInfo is PropertyInfo; 161 } 162 163 public static Type GetAccessorType(this MemberInfo memberInfo) 164 { 165 if (memberInfo is FieldInfo) 166 { 167 return ((FieldInfo)memberInfo).FieldType; 168 } 169 else if (memberInfo is PropertyInfo) 170 { 171 return ((PropertyInfo)memberInfo).PropertyType; 172 } 173 else 174 { 175 return null; 176 } 177 } 178 179 public static bool IsPubliclyGettable(this MemberInfo memberInfo) 180 { 181 if (memberInfo is FieldInfo) 182 { 183 return ((FieldInfo)memberInfo).IsPublic; 184 } 185 else if (memberInfo is PropertyInfo) 186 { 187 var propertyInfo = (PropertyInfo)memberInfo; 188 189 return propertyInfo.CanRead && propertyInfo.GetGetMethod(false) != null; 190 } 191 else if (memberInfo is MethodInfo) 192 { 193 return ((MethodInfo)memberInfo).IsPublic; 194 } 195 else if (memberInfo is ConstructorInfo) 196 { 197 return ((ConstructorInfo)memberInfo).IsPublic; 198 } 199 else 200 { 201 throw new NotSupportedException(); 202 } 203 } 204 205 private static Type ExtendedDeclaringType(this MemberInfo memberInfo) 206 { 207 if (memberInfo is MethodInfo methodInfo && methodInfo.IsExtension()) 208 { 209 return methodInfo.GetParameters()[0].ParameterType; 210 } 211 else 212 { 213 return memberInfo.DeclaringType; 214 } 215 } 216 217 public static Type ExtendedDeclaringType(this MemberInfo memberInfo, bool invokeAsExtension) 218 { 219 if (invokeAsExtension) 220 { 221 return memberInfo.ExtendedDeclaringType(); 222 } 223 else 224 { 225 return memberInfo.DeclaringType; 226 } 227 } 228 229 public static bool IsStatic(this PropertyInfo propertyInfo) 230 { 231 return (propertyInfo.GetGetMethod(true)?.IsStatic ?? false) || 232 (propertyInfo.GetSetMethod(true)?.IsStatic ?? false); 233 } 234 235 public static bool IsStatic(this MemberInfo memberInfo) 236 { 237 if (memberInfo is FieldInfo) 238 { 239 return ((FieldInfo)memberInfo).IsStatic; 240 } 241 else if (memberInfo is PropertyInfo) 242 { 243 return ((PropertyInfo)memberInfo).IsStatic(); 244 } 245 else if (memberInfo is MethodBase) 246 { 247 return ((MethodBase)memberInfo).IsStatic; 248 } 249 else 250 { 251 throw new NotSupportedException(); 252 } 253 } 254 255 private static IEnumerable<ParameterInfo> GetParametersWithoutThis(this MethodBase methodBase) 256 { 257 return methodBase.GetParameters().Skip(methodBase.IsExtensionMethod() ? 1 : 0); 258 } 259 260 public static bool IsInvokedAsExtension(this MethodBase methodBase, Type targetType) 261 { 262 return methodBase.IsExtensionMethod() && methodBase.DeclaringType != targetType; 263 } 264 265 public static IEnumerable<ParameterInfo> GetInvocationParameters(this MethodBase methodBase, bool invokeAsExtension) 266 { 267 if (invokeAsExtension) 268 { 269 return methodBase.GetParametersWithoutThis(); 270 } 271 else 272 { 273 return methodBase.GetParameters(); 274 } 275 } 276 277 public static IEnumerable<ParameterInfo> GetInvocationParameters(this MethodBase methodBase, Type targetType) 278 { 279 return methodBase.GetInvocationParameters(methodBase.IsInvokedAsExtension(targetType)); 280 } 281 282 public static Type UnderlyingParameterType(this ParameterInfo parameterInfo) 283 { 284 if (parameterInfo.ParameterType.IsByRef) 285 { 286 return parameterInfo.ParameterType.GetElementType(); 287 } 288 else 289 { 290 return parameterInfo.ParameterType; 291 } 292 } 293 294 // https://stackoverflow.com/questions/9977530/ 295 // https://stackoverflow.com/questions/16186694 296 public static bool HasDefaultValue(this ParameterInfo parameterInfo) 297 { 298 return (parameterInfo.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault; 299 } 300 301 public static object DefaultValue(this ParameterInfo parameterInfo) 302 { 303 if (parameterInfo.HasDefaultValue()) 304 { 305 var defaultValue = parameterInfo.DefaultValue; 306 307 // https://stackoverflow.com/questions/45393580 308 if (defaultValue == null && parameterInfo.ParameterType.IsValueType) 309 { 310 defaultValue = parameterInfo.ParameterType.Default(); 311 } 312 313 return defaultValue; 314 } 315 else 316 { 317 return parameterInfo.UnderlyingParameterType().Default(); 318 } 319 } 320 321 public static object PseudoDefaultValue(this ParameterInfo parameterInfo) 322 { 323 if (parameterInfo.HasDefaultValue()) 324 { 325 var defaultValue = parameterInfo.DefaultValue; 326 327 // https://stackoverflow.com/questions/45393580 328 if (defaultValue == null && parameterInfo.ParameterType.IsValueType) 329 { 330 defaultValue = parameterInfo.ParameterType.PseudoDefault(); 331 } 332 333 return defaultValue; 334 } 335 else 336 { 337 return parameterInfo.UnderlyingParameterType().PseudoDefault(); 338 } 339 } 340 341 public static bool AllowsNull(this ParameterInfo parameterInfo) 342 { 343 var type = parameterInfo.ParameterType; 344 345 return (type.IsReferenceType() && parameterInfo.HasAttribute<AllowsNullAttribute>()) || Nullable.GetUnderlyingType(type) != null; 346 } 347 348 // https://stackoverflow.com/questions/30102174/ 349 public static bool HasOutModifier(this ParameterInfo parameterInfo) 350 { 351 Ensure.That(nameof(parameterInfo)).IsNotNull(parameterInfo); 352 353 // Checking for IsOut is not enough, because parameters marked with the [Out] attribute 354 // also return true, while not necessarily having the "out" modifier. This is common for P/Invoke, 355 // for example in Unity's ParticleSystem.GetParticles. 356 return parameterInfo.IsOut && parameterInfo.ParameterType.IsByRef; 357 } 358 359 public static bool CanWrite(this FieldInfo fieldInfo) 360 { 361 return !(fieldInfo.IsInitOnly || fieldInfo.IsLiteral); 362 } 363 364 public static Member ToManipulator(this MemberInfo memberInfo) 365 { 366 return ToManipulator(memberInfo, memberInfo.DeclaringType); 367 } 368 369 public static Member ToManipulator(this MemberInfo memberInfo, Type targetType) 370 { 371 if (memberInfo is FieldInfo fieldInfo) 372 { 373 return fieldInfo.ToManipulator(targetType); 374 } 375 376 if (memberInfo is PropertyInfo propertyInfo) 377 { 378 return propertyInfo.ToManipulator(targetType); 379 } 380 381 if (memberInfo is MethodInfo methodInfo) 382 { 383 return methodInfo.ToManipulator(targetType); 384 } 385 386 if (memberInfo is ConstructorInfo constructorInfo) 387 { 388 return constructorInfo.ToManipulator(targetType); 389 } 390 391 throw new InvalidOperationException(); 392 } 393 394 public static Member ToManipulator(this FieldInfo fieldInfo, Type targetType) 395 { 396 return new Member(targetType, fieldInfo); 397 } 398 399 public static Member ToManipulator(this PropertyInfo propertyInfo, Type targetType) 400 { 401 return new Member(targetType, propertyInfo); 402 } 403 404 public static Member ToManipulator(this MethodInfo methodInfo, Type targetType) 405 { 406 return new Member(targetType, methodInfo); 407 } 408 409 public static Member ToManipulator(this ConstructorInfo constructorInfo, Type targetType) 410 { 411 return new Member(targetType, constructorInfo); 412 } 413 414 public static ConstructorInfo GetConstructorAccepting(this Type type, Type[] paramTypes, bool nonPublic) 415 { 416 var bindingFlags = BindingFlags.Instance | BindingFlags.Public; 417 418 if (nonPublic) 419 { 420 bindingFlags |= BindingFlags.NonPublic; 421 } 422 423 return type 424 .GetConstructors(bindingFlags) 425 .FirstOrDefault(constructor => 426 { 427 var parameters = constructor.GetParameters(); 428 429 if (parameters.Length != paramTypes.Length) 430 { 431 return false; 432 } 433 434 for (var i = 0; i < parameters.Length; i++) 435 { 436 if (paramTypes[i] == null) 437 { 438 if (!parameters[i].ParameterType.IsNullable()) 439 { 440 return false; 441 } 442 } 443 else 444 { 445 if (!parameters[i].ParameterType.IsAssignableFrom(paramTypes[i])) 446 { 447 return false; 448 } 449 } 450 } 451 452 return true; 453 }); 454 } 455 456 public static ConstructorInfo GetConstructorAccepting(this Type type, params Type[] paramTypes) 457 { 458 return GetConstructorAccepting(type, paramTypes, true); 459 } 460 461 public static ConstructorInfo GetPublicConstructorAccepting(this Type type, params Type[] paramTypes) 462 { 463 return GetConstructorAccepting(type, paramTypes, false); 464 } 465 466 public static ConstructorInfo GetDefaultConstructor(this Type type) 467 { 468 return GetConstructorAccepting(type); 469 } 470 471 public static ConstructorInfo GetPublicDefaultConstructor(this Type type) 472 { 473 return GetPublicConstructorAccepting(type); 474 } 475 476 public static MemberInfo[] GetExtendedMember(this Type type, string name, MemberTypes types, BindingFlags flags) 477 { 478 var members = type.GetMember(name, types, flags).ToList(); 479 480 if (types.HasFlag(MemberTypes.Method)) // Check for extension methods 481 { 482 members.AddRange(type.GetExtensionMethods() 483 .Where(extension => extension.Name == name) 484 .Cast<MemberInfo>()); 485 } 486 487 return members.ToArray(); 488 } 489 490 public static MemberInfo[] GetExtendedMembers(this Type type, BindingFlags flags) 491 { 492 var members = type.GetMembers(flags).ToHashSet(); 493 494 foreach (var extensionMethod in type.GetExtensionMethods()) 495 { 496 members.Add(extensionMethod); 497 } 498 499 return members.ToArray(); 500 } 501 502 #region Signature Disambiguation 503 504 private static bool NameMatches(this MemberInfo member, string name) 505 { 506 return member.Name == name; 507 } 508 509 private static bool ParametersMatch(this MethodBase methodBase, IEnumerable<Type> parameterTypes, bool invokeAsExtension) 510 { 511 Ensure.That(nameof(parameterTypes)).IsNotNull(parameterTypes); 512 513 return methodBase.GetInvocationParameters(invokeAsExtension).Select(paramInfo => paramInfo.ParameterType).SequenceEqual(parameterTypes); 514 } 515 516 private static bool GenericArgumentsMatch(this MethodInfo method, IEnumerable<Type> genericArgumentTypes) 517 { 518 Ensure.That(nameof(genericArgumentTypes)).IsNotNull(genericArgumentTypes); 519 520 if (method.ContainsGenericParameters) 521 { 522 return false; 523 } 524 525 return method.GetGenericArguments().SequenceEqual(genericArgumentTypes); 526 } 527 528 public static bool SignatureMatches(this FieldInfo field, string name) 529 { 530 return field.NameMatches(name); 531 } 532 533 public static bool SignatureMatches(this PropertyInfo property, string name) 534 { 535 return property.NameMatches(name); 536 } 537 538 public static bool SignatureMatches(this ConstructorInfo constructor, string name, IEnumerable<Type> parameterTypes) 539 { 540 return constructor.NameMatches(name) && constructor.ParametersMatch(parameterTypes, false); 541 } 542 543 public static bool SignatureMatches(this MethodInfo method, string name, IEnumerable<Type> parameterTypes, bool invokeAsExtension) 544 { 545 return method.NameMatches(name) && method.ParametersMatch(parameterTypes, invokeAsExtension) && !method.ContainsGenericParameters; 546 } 547 548 public static bool SignatureMatches(this MethodInfo method, string name, IEnumerable<Type> parameterTypes, IEnumerable<Type> genericArgumentTypes, bool invokeAsExtension) 549 { 550 return method.NameMatches(name) && method.ParametersMatch(parameterTypes, invokeAsExtension) && method.GenericArgumentsMatch(genericArgumentTypes); 551 } 552 553 public static FieldInfo GetFieldUnambiguous(this Type type, string name, BindingFlags flags) 554 { 555 Ensure.That(nameof(type)).IsNotNull(type); 556 Ensure.That(nameof(name)).IsNotNull(name); 557 558 flags |= BindingFlags.DeclaredOnly; 559 560 while (type != null) 561 { 562 var field = type.GetField(name, flags); 563 564 if (field != null) 565 { 566 return field; 567 } 568 569 type = type.BaseType; 570 } 571 572 return null; 573 } 574 575 public static PropertyInfo GetPropertyUnambiguous(this Type type, string name, BindingFlags flags) 576 { 577 Ensure.That(nameof(type)).IsNotNull(type); 578 Ensure.That(nameof(name)).IsNotNull(name); 579 580 flags |= BindingFlags.DeclaredOnly; 581 582 while (type != null) 583 { 584 var property = type.GetProperty(name, flags); 585 586 if (property != null) 587 { 588 return property; 589 } 590 591 type = type.BaseType; 592 } 593 594 return null; 595 } 596 597 public static MethodInfo GetMethodUnambiguous(this Type type, string name, BindingFlags flags) 598 { 599 Ensure.That(nameof(type)).IsNotNull(type); 600 Ensure.That(nameof(name)).IsNotNull(name); 601 602 flags |= BindingFlags.DeclaredOnly; 603 604 while (type != null) 605 { 606 var method = type.GetMethod(name, flags); 607 608 if (method != null) 609 { 610 return method; 611 } 612 613 type = type.BaseType; 614 } 615 616 return null; 617 } 618 619 private static TMemberInfo DisambiguateHierarchy<TMemberInfo>(this IEnumerable<TMemberInfo> members, Type type) where TMemberInfo : MemberInfo 620 { 621 while (type != null) 622 { 623 foreach (var member in members) 624 { 625 var methodInfo = member as MethodInfo; 626 var invokedAsExtension = methodInfo != null && methodInfo.IsInvokedAsExtension(type); 627 628 if (member.ExtendedDeclaringType(invokedAsExtension) == type) 629 { 630 return member; 631 } 632 } 633 634 type = type.BaseType; 635 } 636 637 return null; 638 } 639 640 public static FieldInfo Disambiguate(this IEnumerable<FieldInfo> fields, Type type) 641 { 642 Ensure.That(nameof(fields)).IsNotNull(fields); 643 Ensure.That(nameof(type)).IsNotNull(type); 644 645 return fields.DisambiguateHierarchy(type); 646 } 647 648 public static PropertyInfo Disambiguate(this IEnumerable<PropertyInfo> properties, Type type) 649 { 650 Ensure.That(nameof(properties)).IsNotNull(properties); 651 Ensure.That(nameof(type)).IsNotNull(type); 652 653 return properties.DisambiguateHierarchy(type); 654 } 655 656 public static ConstructorInfo Disambiguate(this IEnumerable<ConstructorInfo> constructors, Type type, IEnumerable<Type> parameterTypes) 657 { 658 Ensure.That(nameof(constructors)).IsNotNull(constructors); 659 Ensure.That(nameof(type)).IsNotNull(type); 660 Ensure.That(nameof(parameterTypes)).IsNotNull(parameterTypes); 661 662 return constructors.Where(m => m.ParametersMatch(parameterTypes, false) && !m.ContainsGenericParameters).DisambiguateHierarchy(type); 663 } 664 665 public static MethodInfo Disambiguate(this IEnumerable<MethodInfo> methods, Type type, IEnumerable<Type> parameterTypes) 666 { 667 Ensure.That(nameof(methods)).IsNotNull(methods); 668 Ensure.That(nameof(type)).IsNotNull(type); 669 Ensure.That(nameof(parameterTypes)).IsNotNull(parameterTypes); 670 671 return methods.Where(m => m.ParametersMatch(parameterTypes, m.IsInvokedAsExtension(type)) && !m.ContainsGenericParameters).DisambiguateHierarchy(type); 672 } 673 674 public static MethodInfo Disambiguate(this IEnumerable<MethodInfo> methods, Type type, IEnumerable<Type> parameterTypes, IEnumerable<Type> genericArgumentTypes) 675 { 676 Ensure.That(nameof(methods)).IsNotNull(methods); 677 Ensure.That(nameof(type)).IsNotNull(type); 678 Ensure.That(nameof(parameterTypes)).IsNotNull(parameterTypes); 679 Ensure.That(nameof(genericArgumentTypes)).IsNotNull(genericArgumentTypes); 680 681 return methods.Where(m => m.ParametersMatch(parameterTypes, m.IsInvokedAsExtension(type)) && m.GenericArgumentsMatch(genericArgumentTypes)).DisambiguateHierarchy(type); 682 } 683 684 #endregion 685 } 686 687 internal class ExtensionMethodCache 688 { 689 internal ExtensionMethodCache() 690 { 691 // Cache a list of all extension methods in assemblies 692 // http://stackoverflow.com/a/299526 693 Cache = RuntimeCodebase.types 694 .Where(type => type.IsStatic() && !type.IsGenericType && !type.IsNested) 695 .SelectMany(type => type.GetMethods()) 696 .Where(method => method.IsExtension()) 697 .ToArray(); 698 } 699 700 internal readonly MethodInfo[] Cache; 701 } 702}