using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Loader; using System.Text.RegularExpressions; using Mono.Cecil; using Sandbox.Internal; using Sandbox.Upgraders; namespace Sandbox { /// /// See Microsoft.CodeAnalysis.CSharp.Symbols.GeneratedNameKind /// internal enum GeneratedNameKind { None = 0, // Used by EE: ThisProxyField = '4', HoistedLocalField = '5', DisplayClassLocalOrField = '8', LambdaMethod = 'b', LambdaDisplayClass = 'c', StateMachineType = 'd', LocalFunction = 'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names // Used by EnC: AwaiterField = 'u', HoistedSynthesizedLocalField = 's', // Currently not parsed: StateMachineStateField = '1', IteratorCurrentBackingField = '2', StateMachineParameterProxyField = '3', ReusableHoistedLocalField = '7', LambdaCacheField = '9', FixedBufferField = 'e', Extension = 'E', FileType = 'F', AnonymousType = 'f', TransparentIdentifier = 'h', AnonymousTypeField = 'i', StateMachineStateIdField = 'I', AnonymousTypeTypeParameter = 'j', AutoPropertyBackingField = 'k', IteratorCurrentThreadIdField = 'l', IteratorFinallyMethod = 'm', BaseMethodWrapper = 'n', AsyncBuilderField = 't', DelegateCacheContainerType = 'O', DynamicCallSiteContainerType = 'o', PrimaryConstructorParameter = 'P', DynamicCallSiteField = 'p', AsyncIteratorPromiseOfValueOrEndBackingField = 'v', DisposeModeField = 'w', CombinedTokensField = 'x', InlineArrayType = 'y', ReadOnlyListType = 'z', // last } /// /// See Microsoft.CodeAnalysis.CSharp.Symbols.GeneratedNames.MakeMethodScopedSynthesizedName /// internal record struct GeneratedName( string ScopeName, GeneratedNameKind Kind, string Suffix, int Ordinal1, int Generation1, int Ordinal2, int Generation2, int TypeParameterCount ) { private static readonly Regex Regex = new Regex( @" ^<(?[^>]+)?> (?[a-z0-9]) ( __(?:(?[A-Za-z_][A-Za-z0-9_]+)\||(?[A-Za-z_]+))? ( (?[0-9]+)(\#(?[0-9]+))? ( _(?[0-9]+)(\#(?[0-9]+))? )? )? )?(`(?[0-9]+))?$ ", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace ); public static bool TryParse( string value, out GeneratedName result ) { var match = Regex.Match( value ); if ( !match.Success ) { result = default; return false; } result = new GeneratedName( ScopeName: match.Groups["scopeName"].Success ? match.Groups["scopeName"].Value : null, Kind: (GeneratedNameKind)match.Groups["kind"].Value[0], Suffix: match.Groups["suffix"].Success ? match.Groups["suffix"].Value : null, Ordinal1: match.Groups["ordinal1"].Success ? int.Parse( match.Groups["ordinal1"].Value ) : -1, Generation1: match.Groups["generation1"].Success ? int.Parse( match.Groups["generation1"].Value ) : -1, Ordinal2: match.Groups["ordinal2"].Success ? int.Parse( match.Groups["ordinal2"].Value ) : -1, Generation2: match.Groups["generation2"].Success ? int.Parse( match.Groups["generation2"].Value ) : -1, TypeParameterCount: match.Groups["typeParams"].Success ? int.Parse( match.Groups["typeParams"].Value ) : 0 ); return true; } } partial class Hotload { private Dictionary ScopeMethodOrdinals { get; } = new Dictionary(); private static IEnumerable GetMethods( Type type, string methodName ) { var bFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; switch ( methodName ) { case null: return type.GetConstructors( bFlags | BindingFlags.Instance | BindingFlags.Static ) .Cast() .Union( type.GetMethods( bFlags | BindingFlags.Instance | BindingFlags.Static ) ); case ".ctor": return type.GetConstructors( bFlags | BindingFlags.Instance ); case ".cctor": return type.GetConstructors( bFlags | BindingFlags.Static ); default: return type.GetMethods( bFlags | BindingFlags.Static | BindingFlags.Instance ) .Where( x => x.Name == methodName ); } } private static bool MatchesScopeMethodName( MethodBase method, string scopeMethodName ) { if ( scopeMethodName == null ) { return true; } if ( method.Name == scopeMethodName ) { return true; } if ( !DelegateUpgrader.IsCompilerGenerated( method.Name ) ) { return false; } if ( !GeneratedName.TryParse( method.Name, out var name ) || name.Kind != GeneratedNameKind.LocalFunction ) { return false; } return name.ScopeName == scopeMethodName; } private MethodBase FindScopeMethod( Type declaringType, string scopeMethodName, int scopeMethodOrdinal ) { Assert.True( scopeMethodOrdinal >= 0 ); var methods = GetMethods( declaringType, scopeMethodName ); foreach ( var method in methods ) { var ordinal = GetScopeMethodOrdinal( method ); if ( ordinal == scopeMethodOrdinal ) { return method; } } Log( HotloadEntryType.Warning, $"Unable to find scope method (Name: {scopeMethodName}, Ordinal: {scopeMethodOrdinal})", declaringType ); return null; } private static int ParseScopeMethodOrdinal( MethodReference lambdaMethodRef ) { if ( !DelegateUpgrader.GetLambdaMethodInfo( lambdaMethodRef, out _, out _, out var nameInfo ) ) { return -1; } return nameInfo.ScopeMethodOrdinal; } private int GetScopeMethodOrdinal( MethodBase scopeMethod ) { if ( ScopeMethodOrdinals.TryGetValue( scopeMethod, out var ordinal ) ) { return ordinal; } ordinal = -1; Assert.NotNull( scopeMethod.DeclaringType ); if ( scopeMethod.GetCustomAttribute() is { } stateMachineAttribute ) { if ( GeneratedName.TryParse( stateMachineAttribute.StateMachineType.Name, out var stateMachineTypeName ) ) { Assert.AreEqual( GeneratedNameKind.StateMachineType, stateMachineTypeName.Kind ); Assert.True( stateMachineTypeName.Ordinal1 >= 0 ); return stateMachineTypeName.Ordinal1; } } var scopeMethodDef = GetMethodDefinition( scopeMethod ); if ( scopeMethodDef is { HasBody: true } ) { foreach ( var inst in scopeMethodDef.Body.Instructions ) { if ( inst.Operand is not MethodReference methodRef ) { continue; } var parsedOrdinal = ParseScopeMethodOrdinal( methodRef ); if ( parsedOrdinal == -1 ) { continue; } Assert.True( ordinal == -1 || ordinal == parsedOrdinal ); ordinal = parsedOrdinal; } } return ScopeMethodOrdinals[scopeMethod] = ordinal; } private Type GetNewNestedGeneratedType( Type oldType, GeneratedName oldName ) { var oldDeclaringType = oldType.DeclaringType; var newDeclaringType = GetNewType( oldDeclaringType ); if ( newDeclaringType == null ) { return null; } if ( oldName.Ordinal1 == -1 && oldName.Ordinal2 == -1 ) { return newDeclaringType.GetNestedType( oldType.Name, BindingFlags.Public | BindingFlags.NonPublic ); } var oldScopeMethod = FindScopeMethod( oldDeclaringType, null, oldName.Ordinal1 ); Assert.NotNull( oldScopeMethod ); var newScopeMethod = GetNewInstance( oldScopeMethod ) as MethodBase; if ( newScopeMethod == null ) { return null; } var newScopeOrdinal = GetScopeMethodOrdinal( newScopeMethod ); return newDeclaringType .GetNestedTypes( BindingFlags.Public | BindingFlags.NonPublic ) .FirstOrDefault( x => GeneratedName.TryParse( x.Name, out var newName ) && newName.Kind == oldName.Kind && newName.Suffix == oldName.Suffix && newName.Ordinal1 == newScopeOrdinal && newName.Ordinal2 == oldName.Ordinal2 ); } private Type GetNewReadOnlyListType( Type oldType ) { // This method expects a generic type definition because readonly list types are always generic. // Passing a constructed type or non-generic type would be incorrect and may cause runtime errors. Assert.True( oldType.IsGenericTypeDefinition ); // Assembly not hotloaded, so type hasn't changed if ( !Swaps.TryGetValue( oldType.Assembly, out var newAsm ) ) return oldType; // Assembly was unloaded, so replace with null if ( newAsm is null ) return null; // Just match by name, easy return oldType.FullName is not { } fullName ? null : newAsm.GetType( fullName ); } /// /// Anonymous types are generic types with a type parameter for each property's type. /// That means the compiler will reuse the same type definition as long as property names match. /// /// Generic type definition for the anonymous type. /// Names of the anonymous type's properties. private readonly record struct AnonymousTypeInfo( Type Type, ImmutableArray PropertyNames ) { /// /// Anonymous types are equivalent as long as the property names match. /// public bool HasMatchingPropertyNames( AnonymousTypeInfo other ) { if ( PropertyNames.Length != other.PropertyNames.Length ) return false; for ( var i = 0; i < PropertyNames.Length; ++i ) { var a = PropertyNames[i]; var b = other.PropertyNames[i]; if ( !a.Equals( b, StringComparison.Ordinal ) ) return false; } return true; } } [GeneratedRegex( "^<(?[^>]+)>j__TPar$" )] private static partial Regex AnonymousTypeParamNameRegex { get; } /// /// If is an anonymous type, gets a description of its properties. /// Otherwise, returns null. /// private AnonymousTypeInfo? GetAnonymousTypeInfo( Type type ) { if ( !type.IsGenericTypeDefinition ) return null; if ( !type.Name.StartsWith( "<>f__AnonymousType" ) ) return null; if ( type.GetGenericArguments() is not { Length: > 0 } genericArgs ) return null; var propertyNames = new string[genericArgs.Length]; for ( var i = 0; i < genericArgs.Length; ++i ) { if ( AnonymousTypeParamNameRegex.Match( genericArgs[i].Name ) is not { Success: true } match ) { return null; } propertyNames[i] = match.Groups["name"].Value; } return new AnonymousTypeInfo( type, [.. propertyNames] ); } private Dictionary> AnonymousTypes { get; } = new(); private ImmutableArray GetAnonymousTypes( Assembly asm ) { if ( AnonymousTypes.TryGetValue( asm, out var types ) ) return types; return AnonymousTypes[asm] = [ ..asm.GetTypes() .Select( GetAnonymousTypeInfo ) .Where( x => x is not null ) .Select( x => x!.Value ) ]; } /// /// Find an anonymous type in a hotloaded assembly with matching property names, or null if not found. /// private Type GetNewAnonymousType( Type oldType ) { Assert.True( oldType.IsGenericTypeDefinition ); // Assembly not hotloaded, so type hasn't changed if ( !Swaps.TryGetValue( oldType.Assembly, out var newAsm ) ) return oldType; // Assembly was unloaded, so replace with null if ( newAsm is null ) return null; if ( GetAnonymousTypeInfo( oldType ) is not { } oldTypeInfo ) return null; foreach ( var newTypeInfo in GetAnonymousTypes( newAsm ) ) { if ( newTypeInfo.HasMatchingPropertyNames( oldTypeInfo ) ) { return newTypeInfo.Type; } } // No matches found, will have to replace with null Log( HotloadEntryType.Warning, $"Couldn't find exact replacement for anonymous type {{ {string.Join( ", ", oldTypeInfo.PropertyNames )} }}.", oldType ); return null; } } internal static partial class HotloadReflectionExtensions { public static bool IsBackingField( this FieldInfo fieldInfo ) { return GeneratedName.TryParse( fieldInfo.Name, out var name ) && name.Kind == GeneratedNameKind.AutoPropertyBackingField; } public static bool TryGetBackedProperty( this FieldInfo fieldInfo, out PropertyInfo propertyInfo ) { if ( !GeneratedName.TryParse( fieldInfo.Name, out var name ) || name.Kind != GeneratedNameKind.AutoPropertyBackingField ) { propertyInfo = default; return false; } propertyInfo = fieldInfo.DeclaringType!.GetProperty( name.ScopeName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly ); return propertyInfo != null; } } #if NET_CORE internal static partial class ReflectionExtensions { private static AssemblyNameReference GetAssemblyName( this IMetadataScope scope ) { switch ( scope.MetadataScopeType ) { case MetadataScopeType.AssemblyNameReference: return (AssemblyNameReference)scope; case MetadataScopeType.ModuleDefinition: return ((ModuleDefinition)scope).Assembly.Name; default: throw new NotImplementedException(); } } private static string GetAssemblyFullName( this IMetadataScope scope ) { switch ( scope.MetadataScopeType ) { case MetadataScopeType.AssemblyNameReference: return ((AssemblyNameReference)scope).FullName; case MetadataScopeType.ModuleDefinition: return ((ModuleDefinition)scope).Assembly.FullName; default: throw new NotImplementedException(); } } private static bool IsMatchingAssembly( Assembly asm, AssemblyName name ) { var asmName = asm.GetName(); return asmName.Name == name.Name && (asmName.Version?.Equals( name.Version ) ?? false); } public static Type ResolveType( this Module module, TypeReference typeRef ) { Assembly asm; var asmNameRef = typeRef.Scope.GetAssemblyName(); if ( module.Assembly.GetName().Name == asmNameRef.Name ) { asm = module.Assembly; } else { // Prefer to exactly match an assembly version referenced by this module var matchingName = module.Assembly.GetReferencedAssemblies() .FirstOrDefault( x => x.Name == asmNameRef.Name ); var asmName = matchingName ?? new AssemblyName( asmNameRef.FullName ); var loadCtx = AssemblyLoadContext.GetLoadContext( module.Assembly )!; asm = loadCtx.Assemblies.FirstOrDefault( x => IsMatchingAssembly( x, asmName ) ) ?? loadCtx!.LoadFromAssemblyName( asmName ); } if ( typeRef.IsGenericInstance && typeRef is GenericInstanceType genericType ) { var elemType = module.ResolveType( genericType.ElementType ); var typeArgs = genericType.GenericArguments.Select( x => module.ResolveType( x ) ).ToArray(); return elemType.MakeGenericType( typeArgs ); } if ( typeRef.IsArray && typeRef is ArrayType arrayType ) { var elemType = module.ResolveType( arrayType.ElementType ); return arrayType.Rank == 1 ? elemType.MakeArrayType() : elemType.MakeArrayType( arrayType.Rank ); } if ( typeRef.IsByReference && typeRef is ByReferenceType byRefType ) { var elemType = module.ResolveType( byRefType.ElementType ); return elemType.MakeByRefType(); } if ( typeRef.IsNested ) { var declType = module.ResolveType( typeRef.DeclaringType ); return declType.GetNestedType( typeRef.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly ); } return asm.GetType( typeRef.FullName, true, false ); } public static FieldInfo ResolveField( this Module module, FieldReference fieldRef ) { var declaringType = module.ResolveType( fieldRef.DeclaringType ); return declaringType.GetField( fieldRef.Name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly ); } public static MethodBase ResolveMethod( this Module module, MethodReference methodRef ) { var declaringType = module.ResolveType( methodRef.DeclaringType ); MethodBase[] candidates; var isGenericInstance = false; var elementMethodRef = methodRef; if ( methodRef is GenericInstanceMethod genericInstance ) { isGenericInstance = true; elementMethodRef = genericInstance.ElementMethod; } switch ( methodRef.Name ) { case ".ctor": candidates = declaringType .GetConstructors( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly ) .Where( x => x.GetParameters().Length == methodRef.Parameters.Count ) .ToArray(); break; case ".cctor": candidates = declaringType .GetConstructors( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly ) .Where( x => x.GetParameters().Length == methodRef.Parameters.Count ) .ToArray(); break; default: candidates = declaringType .GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly ) .Where( x => x.Name == methodRef.Name && x.GetParameters().Length == methodRef.Parameters.Count && isGenericInstance == x.IsGenericMethodDefinition ) .ToArray(); break; } var resolvedParamerTypes = elementMethodRef.Parameters .Select( x => Hotload.ResolveGenericArguments( elementMethodRef, x.ParameterType ) ) .ToArray(); var match = candidates .OrderBy( x => x.GetParameters().Count( y => y.ParameterType.IsGenericParameter ) ) .FirstOrDefault( candidate => { var parameters = candidate.GetParameters(); for ( var i = 0; i < parameters.Length; i++ ) { if ( !Hotload.IsMatchingType( resolvedParamerTypes[i], parameters[i].ParameterType ) ) { return false; } } return true; } ); if ( match == null ) { throw new Exception( $"Unable to resolve method.\n Method Ref: {methodRef}\n Candidates:\n{string.Join( "\n", candidates.Select( x => $" {x}" ) )}" ); } if ( match is MethodInfo { IsGenericMethodDefinition: true } method && methodRef is GenericInstanceMethod genericMethodRef ) { var typeArgs = genericMethodRef.GenericArguments .Select( module.ResolveType ) .ToArray(); return method.MakeGenericMethod( typeArgs ); } return match; } public static bool HasAttribute( this FieldInfo fieldInfo ) where T : Attribute { if ( fieldInfo == null ) return false; if ( fieldInfo.GetCustomAttribute() != null ) return true; if ( !fieldInfo.IsPrivate || fieldInfo.GetCustomAttribute() == null ) { return false; } // If this is a backing field, check the property for a SkipHotload attrib var match = DefaultUpgrader.BackingFieldRegex.Match( fieldInfo.Name ); if ( !match.Success ) return false; var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | (fieldInfo.IsStatic ? BindingFlags.Static : BindingFlags.Instance); var property = fieldInfo.DeclaringType?.GetProperty( match.Groups["name"].Value, flags ); return property != null && property.GetCustomAttribute() != null; } } public class MemberEqualityComparer : IEqualityComparer { private readonly ConcurrentDictionary<(MemberInfo X, MemberInfo Y), bool> _cache = new(); public bool Equals( MemberInfo x, MemberInfo y ) { if ( x == y ) { return true; } if ( x == null || y == null ) { return false; } if ( _sInProgress.Contains( (x, y) ) ) { return true; } if ( x.Name != y.Name ) { return false; } if ( x.Module.Assembly.GetName().Name != y.Module.Assembly.GetName().Name ) { return false; } if ( !Equals( x.DeclaringType, y.DeclaringType ) ) { return false; } if ( _cache.TryGetValue( (x, y), out var cached ) ) { return cached; } return _cache[(x, y)] = _cache[(y, x)] = EqualsUncached( x, y ); } [ThreadStatic] private static HashSet<(MemberInfo X, MemberInfo Y)> _sInProgress; public bool AllMembersEqual( Type x, Type y ) { if ( x == y ) { return true; } const BindingFlags AllDeclared = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; var xMembers = x.GetMembers( AllDeclared ); var yMembers = y.GetMembers( AllDeclared ); if ( xMembers.Length != yMembers.Length ) { return false; } _sInProgress ??= new HashSet<(MemberInfo, MemberInfo)>(); Assert.True( _sInProgress.Add( (x, y) ) ); try { for ( var i = 0; i < xMembers.Length; i++ ) { var xMember = xMembers[i]; var yMember = yMembers[i]; if ( !Equals( xMember, yMember ) ) { return false; } } } finally { _sInProgress.Remove( (x, y) ); } return Equals( x.BaseType, y.BaseType ); } private bool EqualsUncached( MemberInfo x, MemberInfo y ) { _sInProgress ??= new HashSet<(MemberInfo, MemberInfo)>(); // Avoid stack overflow if we recurse and check the same thing again if ( !_sInProgress.Add( (x, y) ) ) { return true; } try { return AllAttributesEqual( x, y ) && x switch { Type typeX when y is Type typeY => EqualsUncached( typeX, typeY ), FieldInfo fieldX when y is FieldInfo fieldY => EqualsUncached( fieldX, fieldY ), MethodBase methodX when y is MethodBase methodY => EqualsUncached( methodX, methodY ), PropertyInfo propertyX when y is PropertyInfo propertyY => EqualsUncached( propertyX, propertyY ), EventInfo eventX when y is EventInfo eventY => EqualsUncached( eventX, eventY ), _ => false }; } finally { _sInProgress.Remove( (x, y) ); } } private static HashSet IgnoredAttributeTypes { get; } = new() { typeof(SupportsILHotloadAttribute), typeof(MethodBodyChangeAttribute), typeof(PropertyAccessorBodyChangeAttribute), typeof(SourceLocationAttribute), typeof(ClassFileLocationAttribute) }; private bool AllAttributesEqual( MemberInfo x, MemberInfo y ) { var xAttribs = x.GetCustomAttributesData() .Where( attrib => !IgnoredAttributeTypes.Contains( attrib.AttributeType ) ) .ToArray(); var yAttribs = y.GetCustomAttributesData() .Where( attrib => !IgnoredAttributeTypes.Contains( attrib.AttributeType ) ) .ToArray(); if ( xAttribs.Length != yAttribs.Length ) { return false; } for ( var i = 0; i < xAttribs.Length; ++i ) { var xAttrib = xAttribs[i]; var yAttrib = yAttribs[i]; if ( !Equals( xAttrib.AttributeType, yAttrib.AttributeType ) ) { return false; } if ( !Equals( xAttrib.Constructor, yAttrib.Constructor ) ) { return false; } if ( xAttrib.ConstructorArguments.Count != yAttrib.ConstructorArguments.Count ) { return false; } for ( var j = 0; j < xAttrib.ConstructorArguments.Count; ++j ) { var xArg = xAttrib.ConstructorArguments[j]; var yArg = yAttrib.ConstructorArguments[j]; if ( !AttribValueEquals( xArg, yArg ) ) { return false; } } if ( xAttrib.NamedArguments?.Count != yAttrib.NamedArguments?.Count ) { return false; } if ( xAttrib.NamedArguments == null || yAttrib.NamedArguments == null ) { continue; } for ( var j = 0; j < xAttrib.NamedArguments.Count; ++j ) { var xArg = xAttrib.NamedArguments[j]; var yArg = yAttrib.NamedArguments[j]; if ( !Equals( xArg.MemberInfo, yArg.MemberInfo ) ) { return false; } if ( !AttribValueEquals( xArg.TypedValue, yArg.TypedValue ) ) { return false; } } } return true; } private bool AttribValueEquals( object x, object y ) { if ( x is CustomAttributeTypedArgument xArg && y is CustomAttributeTypedArgument yArg ) { if ( !Equals( xArg.ArgumentType, yArg.ArgumentType ) ) { return false; } x = xArg.Value; y = yArg.Value; } if ( x is IList xArray && y is IList yArray ) { if ( xArray.Count != yArray.Count ) { return false; } for ( var i = 0; i < xArray.Count; ++i ) { if ( !AttribValueEquals( xArray[i], yArray[i] ) ) { return false; } } return true; } if ( x is Type xType && y is Type yType ) { return Equals( xType, yType ); } if ( x is string xString && y is string yString ) { return string.Equals( xString, yString, StringComparison.Ordinal ); } return Equals( x, y ); } private bool EqualsUncached( Type x, Type y ) { if ( x.Attributes != y.Attributes ) { return false; } if ( x.IsValueType != y.IsValueType || x.IsNested != y.IsNested ) { return false; } if ( x.IsArray || y.IsArray ) { return x.IsArray && y.IsArray && x.GetArrayRank() == y.GetArrayRank() && Equals( x.GetElementType(), y.GetElementType() ); } if ( x.IsByRef || y.IsByRef ) { return x.IsByRef && y.IsByRef && Equals( x.GetElementType(), y.GetElementType() ); } if ( x.IsPointer || y.IsPointer ) { return x.IsPointer && y.IsPointer && Equals( x.GetElementType(), y.GetElementType() ); } if ( x.IsConstructedGenericType || y.IsConstructedGenericType ) { return x.IsConstructedGenericType && y.IsConstructedGenericType && Equals( x.GetGenericTypeDefinition(), y.GetGenericTypeDefinition() ) && EqualsUncached( x.GetGenericArguments(), y.GetGenericArguments() ); } if ( x.HasElementType || y.HasElementType ) { // TODO: better safe than sorry return false; } if ( x.IsGenericTypeParameter || y.IsGenericTypeParameter ) { return x.IsGenericTypeParameter && y.IsGenericTypeParameter && x.GenericParameterPosition == y.GenericParameterPosition; } if ( x.IsGenericMethodParameter || y.IsGenericMethodParameter ) { return x.IsGenericMethodParameter && y.IsGenericMethodParameter && x.GenericParameterPosition == y.GenericParameterPosition && Equals( x.DeclaringMethod, y.DeclaringMethod ); } if ( x.IsGenericTypeDefinition || y.IsGenericTypeDefinition ) { return x.IsGenericTypeDefinition && y.IsGenericTypeDefinition && EqualsUncached( x.GetGenericArguments(), y.GetGenericArguments() ); } return true; } private bool EqualsUncached( FieldInfo x, FieldInfo y ) { if ( x.Attributes != y.Attributes ) { return false; } if ( !Equals( x.FieldType, y.FieldType ) ) { return false; } return true; } private bool EqualsUncached( MethodBase x, MethodBase y ) { if ( x.Attributes != y.Attributes ) { return false; } if ( x is MethodInfo || y is MethodInfo ) { if ( x is not MethodInfo methodX || y is not MethodInfo methodY ) { return false; } if ( !Equals( methodX.ReturnType, methodY.ReturnType ) ) { return false; } } if ( x.IsAbstract != y.IsAbstract ) { return false; } var paramsX = x.GetParameters(); var paramsY = y.GetParameters(); if ( paramsX.Length != paramsY.Length ) { return false; } for ( var i = 0; i < paramsX.Length; ++i ) { if ( !Equals( paramsX[i], paramsY[i] ) ) { return false; } } return true; } private bool Equals( ParameterInfo x, ParameterInfo y ) { if ( x.Attributes != y.Attributes ) { return false; } if ( x.Name != y.Name ) { return false; } if ( !Equals( x.ParameterType, y.ParameterType ) ) { return false; } return true; } private bool EqualsUncached( PropertyInfo x, PropertyInfo y ) { if ( x.Attributes != y.Attributes ) { return false; } if ( !Equals( x.PropertyType, y.PropertyType ) ) { return false; } return true; } private bool EqualsUncached( EventInfo x, EventInfo y ) { if ( x.Attributes != y.Attributes ) { return false; } if ( !Equals( x.EventHandlerType, y.EventHandlerType ) ) { return false; } return true; } private bool EqualsUncached( IReadOnlyList x, IReadOnlyList y ) { if ( x.Count != y.Count ) { return false; } for ( var i = 0; i < x.Count; ++i ) { if ( !Equals( x[i], y[i] ) ) { return false; } } return true; } public int GetHashCode( MemberInfo obj ) { throw new NotImplementedException(); } } internal static class MonoCecilExtensions { public static IEnumerable GetConstructors( this Mono.Cecil.TypeDefinition typeDef ) { return typeDef.Methods.Where( x => x.IsConstructor ); } } #elif NET_4_6 internal static partial class ReflectionExtensions { public static Delegate CreateDelegate( this MethodInfo method, Type delegType ) { return Delegate.CreateDelegate( delegType, method ); } public static Delegate CreateDelegate( this MethodInfo method, Type delegType, object target ) { return Delegate.CreateDelegate( delegType, target, method ); } public static MethodInfo GetMethodInfo( this Delegate deleg ) { return deleg.Method; } public static Type GetTypeInfo( this Type type ) { return type; } public static TAttrib GetCustomAttribute( this MemberInfo member, bool inherit = false ) where TAttrib : Attribute { return (TAttrib) member.GetCustomAttributes( typeof (TAttrib), inherit ).FirstOrDefault(); } } #endif #if !NET_CORE internal static partial class ReflectionExtensions { public static Type ResolveType( this Module module, TypeReference typeRef ) { return module.ResolveType( typeRef.MetadataToken.ToInt32() ); } public static FieldInfo ResolveField( this Module module, FieldReference fieldRef ) { return module.ResolveField( fieldRef.MetadataToken.ToInt32() ); } public static MethodBase ResolveMethod( this Module module, MethodReference methodRef ) { return module.ResolveMethod( methodRef.MetadataToken.ToInt32() ); } } #endif }