Advertisement
Guest User

Untitled

a guest
Jan 23rd, 2019
105
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 18.08 KB | None | 0 0
  1. public static class StructuralHashCodeCalculator
  2. {
  3.     private static readonly ConcurrentDictionary<Type, Func<object, int>> methodCache;
  4.  
  5.     static StructuralHashCodeCalculator()
  6.     {
  7.         methodCache = new ConcurrentDictionary<Type, Func<object, int>>();
  8.     }
  9.  
  10.     public static int CalculateHashCode<T>([CanBeNull] T instance)
  11.     {
  12.         if (instance == null)
  13.         {
  14.             return 0;
  15.         }
  16.  
  17.         var instanceType = instance.GetType();
  18.         var comparisonMethod = methodCache.GetOrAdd(instanceType, ComparisonMethodGenerator.GenerateHashCodeCalculationMethod);
  19.         return comparisonMethod(instance);
  20.     }
  21. }
  22.  
  23. public static class StructuralComparer
  24. {
  25.     private static readonly ConcurrentDictionary<Type, Func<object, object, bool>> methodCache;
  26.  
  27.     static StructuralComparer()
  28.     {
  29.         methodCache = new ConcurrentDictionary<Type, Func<object, object, bool>>();
  30.     }
  31.  
  32.     public static bool AreEqual<T>([CanBeNull] T first, [CanBeNull] T second)
  33.     {
  34.         if (ReferenceEquals(first, second))
  35.         {
  36.             return true;
  37.         }
  38.  
  39.         if (first == null || second == null)
  40.         {
  41.             return false;
  42.         }
  43.  
  44.         var firstType = first.GetType();
  45.         var secondType = second.GetType();
  46.         if (firstType != secondType)
  47.         {
  48.             throw new InvalidOperationException($"Could not check structural equality of different types {firstType.Name} and {secondType.Name}");
  49.         }
  50.  
  51.         var comparisonMethod = methodCache.GetOrAdd(firstType, ComparisonMethodGenerator.GenerateComparisonMethod);
  52.         return comparisonMethod(first, second);
  53.     }
  54. }
  55.  
  56. public static class ComparisonMethodGenerator
  57. {
  58.     private const int multiplier = 397;
  59.  
  60.     private static readonly Func<object, object, bool> nativeEqualsFunc = (first, second) => first.Equals(second);
  61.     private static readonly Func<object, int> nativeGetHashCodeFunc = (instance) => instance.GetHashCode();
  62.  
  63.     private static readonly Type objectType = typeof(object);
  64.     private static readonly Type boolType = typeof(bool);
  65.     private static readonly Type intType = typeof(int);
  66.     private static readonly Type stringType = typeof(string);
  67.     private static readonly Type enumerableType = typeof(IEnumerable<>);
  68.  
  69.     private static readonly Type comparisonDelegateType = typeof(Func<object, object, bool>);
  70.     private static readonly Type hashCodeCalculationDelegateType = typeof(Func<object, int>);
  71.  
  72.     private static readonly Type comparisonIgnoreAttributeType = typeof(ComparisonIgnoreAttribute);
  73.     private static readonly Type structuralEqualityExtensionsType = typeof(StructuralEqualityExtensions);
  74.     private static readonly Type comparisonMethodGeneratorType = typeof(ComparisonMethodGenerator);
  75.  
  76.     public static Func<object, int> GenerateHashCodeCalculationMethod([NotNull] Type instanceType)
  77.     {
  78.         var сomparableProperties = instanceType.GetProperties().Where(ShouldBeConsidered).ToArray();
  79.         var сomparableFields = instanceType.GetFields().Where(ShouldBeConsidered).ToArray();
  80.         if (сomparableProperties.Length == 0 && сomparableFields.Length == 0)
  81.         {
  82.             return nativeGetHashCodeFunc;
  83.         }
  84.  
  85.         var methodName = $"dynamic_get_hash_code{instanceType.Name}_{Guid.NewGuid():N}";
  86.         var dynamicMethod = new DynamicMethod(methodName, intType, new[] {objectType}, comparisonMethodGeneratorType, true);
  87.         using (var dynamicMethodGenerator = new GroboIL(dynamicMethod))
  88.         {
  89.             if (IsCollection(instanceType, out _) || IsBuildInType(instanceType))
  90.             {
  91.                 GenerateArgumentHashCodeCalculationMethodBody(instanceType, dynamicMethodGenerator);
  92.  
  93.             }
  94.             else
  95.             {
  96.                 GenerateStructureHashCodeCalculationMethodBody(instanceType, сomparableProperties, сomparableFields, dynamicMethodGenerator);
  97.             }
  98.         }
  99.         var dynamicMethodDelegate = (Func<object, int>)dynamicMethod.CreateDelegate(hashCodeCalculationDelegateType);
  100.         return dynamicMethodDelegate;
  101.     }
  102.  
  103.     private static void GenerateArgumentHashCodeCalculationMethodBody(
  104.         [NotNull] Type comparingObjectType,
  105.         [NotNull] GroboIL ilGenerator)
  106.     {
  107.         IL.LoadArgumentToStack(0, comparingObjectType, ilGenerator);
  108.         IL.HashCode.PutCalculatedHashCodeToStack(comparingObjectType, ilGenerator);
  109.         IL.EmitReturnStackHead(ilGenerator);
  110.     }
  111.  
  112.     private static void GenerateStructureHashCodeCalculationMethodBody(
  113.         [NotNull] Type instanceType,
  114.         [NotNull] PropertyInfo[] comparableProperties,
  115.         [NotNull] FieldInfo[] comparableFields,
  116.         [NotNull] GroboIL ilGenerator)
  117.     {
  118.         var castedToLocal = IL.CreateVariableFromArgument(0, instanceType, ilGenerator);
  119.         var previousHashCodeIsOnStack = false;
  120.         foreach (var instanceProperty in comparableProperties)
  121.         {
  122.             if(previousHashCodeIsOnStack)
  123.             {
  124.                 IL.MultiplyTo(multiplier, ilGenerator);
  125.             }
  126.  
  127.             IL.PutPropertyValueToStack(castedToLocal, instanceType, instanceProperty, ilGenerator);
  128.             var propType = instanceProperty.PropertyType;
  129.             IL.HashCode.PutCalculatedHashCodeToStack(propType, ilGenerator);
  130.  
  131.             if(previousHashCodeIsOnStack)
  132.             {
  133.                 IL.PutXorResultToStack(ilGenerator);
  134.             }
  135.             else
  136.             {
  137.                 previousHashCodeIsOnStack = true;
  138.             }
  139.         }
  140.  
  141.         foreach (var comparableField in comparableFields)
  142.         {
  143.             if(previousHashCodeIsOnStack)
  144.             {
  145.                 IL.MultiplyTo(multiplier, ilGenerator);
  146.             }
  147.  
  148.             IL.PutFieldValueToStack(castedToLocal, instanceType, comparableField, ilGenerator);
  149.             var fieldType = comparableField.FieldType;
  150.             IL.HashCode.PutCalculatedHashCodeToStack(fieldType, ilGenerator);
  151.  
  152.             if(previousHashCodeIsOnStack)
  153.             {
  154.                 IL.PutXorResultToStack(ilGenerator);
  155.             }
  156.             else
  157.             {
  158.                 previousHashCodeIsOnStack = true;
  159.             }
  160.         }
  161.  
  162.         IL.EmitReturnStackHead(ilGenerator);
  163.     }
  164.  
  165.     public static Func<object, object, bool> GenerateComparisonMethod([NotNull] Type comparingObjectType)
  166.     {
  167.         var сomparableProperties = comparingObjectType.GetProperties().Where(ShouldBeConsidered).ToArray();
  168.         var сomparableFields = comparingObjectType.GetFields().Where(ShouldBeConsidered).ToArray();
  169.         if (сomparableProperties.Length == 0 && сomparableFields.Length == 0)
  170.         {
  171.             return nativeEqualsFunc;
  172.         }
  173.  
  174.         var methodName = $"dynamic_are_equal_{comparingObjectType.Name}_{Guid.NewGuid():N}";
  175.         var dynamicMethod = new DynamicMethod(methodName, boolType, new[] {objectType, objectType}, comparisonMethodGeneratorType, true);
  176.         using (var dynamicMethodGenerator = new GroboIL(dynamicMethod))
  177.         {
  178.             if (IsCollection(comparingObjectType, out _) || IsBuildInType(comparingObjectType))
  179.             {
  180.                 GenerateArgumentsComparisonMethodBody(comparingObjectType, dynamicMethodGenerator);
  181.             }
  182.             else
  183.             {
  184.                 GenerateStructureComparisonMethodBody(comparingObjectType, сomparableProperties, сomparableFields, dynamicMethodGenerator);
  185.             }
  186.         }
  187.         var dynamicMethodDelegate = (Func<object, object, bool>)dynamicMethod.CreateDelegate(comparisonDelegateType);
  188.         return dynamicMethodDelegate;
  189.     }
  190.  
  191.     private static void GenerateArgumentsComparisonMethodBody(
  192.         [NotNull] Type comparingObjectType,
  193.         [NotNull] GroboIL ilGenerator)
  194.     {
  195.         IL.LoadArgumentToStack(0, comparingObjectType, ilGenerator);
  196.         IL.LoadArgumentToStack(1, comparingObjectType, ilGenerator);
  197.  
  198.         IL.Comparison.PutComparisonResultToStack(comparingObjectType, ilGenerator);
  199.         IL.Comparison.EmitReturnFalseIfNotEquals(ilGenerator);
  200.  
  201.         IL.EmitReturnBool(true, ilGenerator);
  202.     }
  203.  
  204.     private static void GenerateStructureComparisonMethodBody(
  205.         [NotNull] Type comparingObjectType,
  206.         [NotNull] PropertyInfo[] comparableProperties,
  207.         [NotNull] FieldInfo[] comparableFields,
  208.         [NotNull] GroboIL ilGenerator)
  209.     {
  210.         var castedToLocal = IL.CreateVariableFromArgument(0, comparingObjectType, ilGenerator);
  211.         var castedFromLocal = IL.CreateVariableFromArgument(1, comparingObjectType, ilGenerator);
  212.  
  213.         foreach (var comparableProperty in comparableProperties)
  214.         {
  215.             IL.PutPropertyValueToStack(castedToLocal, comparingObjectType, comparableProperty, ilGenerator);
  216.             IL.PutPropertyValueToStack(castedFromLocal, comparingObjectType, comparableProperty, ilGenerator);
  217.  
  218.             var propType = comparableProperty.PropertyType;
  219.             IL.Comparison.PutComparisonResultToStack(propType, ilGenerator);
  220.             IL.Comparison.EmitReturnFalseIfNotEquals(ilGenerator);
  221.         }
  222.  
  223.         foreach (var comparableField in comparableFields)
  224.         {
  225.             IL.PutFieldValueToStack(castedToLocal, comparingObjectType, comparableField, ilGenerator);
  226.             IL.PutFieldValueToStack(castedFromLocal, comparingObjectType, comparableField, ilGenerator);
  227.  
  228.             var fieldType = comparableField.FieldType;
  229.             IL.Comparison.PutComparisonResultToStack(fieldType, ilGenerator);
  230.             IL.Comparison.EmitReturnFalseIfNotEquals(ilGenerator);
  231.         }
  232.  
  233.         IL.EmitReturnBool(true, ilGenerator);
  234.     }
  235.  
  236.     private static bool IsCollection([NotNull] Type type, out Type itemType)
  237.     {
  238.         itemType = null;
  239.         if (type == stringType)
  240.         {
  241.             return false;
  242.         }
  243.  
  244.         //note: http://stackoverflow.com/questions/9434825/determine-if-a-property-is-a-kind-of-array-by-reflection
  245.         var iEnumerableMemberInfo = type.GetInterface(enumerableType.FullName);
  246.         if (iEnumerableMemberInfo != null)
  247.         {
  248.             itemType = iEnumerableMemberInfo.GetGenericArguments()[0];
  249.             return true;
  250.         }
  251.         return false;
  252.     }
  253.  
  254.     private static bool IsBuildInType([NotNull] Type type)
  255.     {
  256.         //How to determine build-in type in .net
  257.         //https://stackoverflow.com/questions/5932580/how-to-determine-if-a-object-type-is-a-built-in-system-type
  258.         return type.Module.ScopeName == "CommonLanguageRuntimeLibrary";
  259.     }
  260.  
  261.     private static bool ShouldBeConsidered([NotNull] FieldInfo field)
  262.     {
  263.         return field.IsPublic && !field.IsStatic && !IsIgnored(field);
  264.     }
  265.  
  266.     private static bool IsIgnored([NotNull] FieldInfo field)
  267.     {
  268.         return field.FindAttribute<ComparisonIgnoreAttribute>() != null;
  269.     }
  270.  
  271.     private static bool ShouldBeConsidered([NotNull] PropertyInfo property)
  272.     {
  273.         return property.CanRead && property.GetMethod.IsPublic && !IsIgnored(property);
  274.     }
  275.  
  276.     private static bool IsIgnored([NotNull] PropertyInfo property)
  277.     {
  278.         return property.GetCustomAttributes(comparisonIgnoreAttributeType, true).Length > 0;
  279.     }
  280.  
  281.     private static class IL
  282.     {
  283.         public static void LoadArgumentToStack(int argumentIndex, [NotNull] Type argumentType, [NotNull] GroboIL ilGenerator)
  284.         {
  285.             ilGenerator.Ldarg(argumentIndex);
  286.             if (argumentType.IsValueType)
  287.             {
  288.                 ilGenerator.Unbox_Any(argumentType);
  289.             }
  290.             else
  291.             {
  292.                 ilGenerator.Castclass(argumentType);
  293.             }
  294.         }
  295.  
  296.         public static GroboIL.Local CreateVariableFromArgument(int argumentIndex, [NotNull] Type argumentType, [NotNull] GroboIL ilGenerator)
  297.         {
  298.             var variable = ilGenerator.DeclareLocal(argumentType);
  299.             LoadArgumentToStack(argumentIndex, argumentType, ilGenerator);
  300.             ilGenerator.Stloc(variable);
  301.  
  302.             return variable;
  303.         }
  304.  
  305.         public static void MultiplyTo(int value, [NotNull] GroboIL ilGenerator)
  306.         {
  307.             ilGenerator.Ldc_I4(value);
  308.             ilGenerator.Mul();
  309.         }
  310.  
  311.         public static void PutFieldValueToStack(
  312.             GroboIL.Local variable,
  313.             [NotNull] Type variableType,
  314.             FieldInfo variableField,
  315.             [NotNull] GroboIL ilGenerator)
  316.         {
  317.             if (variableType.IsValueType)
  318.             {
  319.                 ilGenerator.Ldloca(variable);
  320.             }
  321.             else
  322.             {
  323.                 ilGenerator.Ldloc(variable);
  324.             }
  325.             ilGenerator.Ldfld(variableField);
  326.         }
  327.  
  328.         public static void PutPropertyValueToStack(
  329.             GroboIL.Local variable,
  330.             [NotNull] Type variableType,
  331.             [NotNull] PropertyInfo variableProperty,
  332.             [NotNull] GroboIL ilGenerator)
  333.         {
  334.             if (variableType.IsValueType)
  335.             {
  336.                 ilGenerator.Ldloca(variable);
  337.             }
  338.             else
  339.             {
  340.                 ilGenerator.Ldloc(variable);
  341.             }
  342.  
  343.             var propertyGetMethod = variableProperty.GetMethod;
  344.             ilGenerator.Call(propertyGetMethod);
  345.         }
  346.  
  347.         public static void EmitReturnBool(bool @bool, [NotNull] GroboIL ilGenerator)
  348.         {
  349.             ilGenerator.Ldc_I4(@bool ? 1 : 0);
  350.             ilGenerator.Ret();
  351.         }
  352.  
  353.         public static void EmitReturnStackHead([NotNull] GroboIL ilGenerator)
  354.         {
  355.             ilGenerator.Ret();
  356.         }
  357.  
  358.         public static void PutXorResultToStack([NotNull] GroboIL ilGenerator)
  359.         {
  360.             ilGenerator.Xor();
  361.         }
  362.  
  363.         public static class Comparison
  364.         {
  365.             private static readonly Type structuralComparerType = typeof(StructuralComparer);
  366.  
  367.             private static readonly MethodInfo isEqualsForNullableMethod = structuralEqualityExtensionsType.GetMethod(nameof(StructuralEqualityExtensions.IsNullableEquals));
  368.             private static readonly MethodInfo isEqualsForRefMethod = structuralEqualityExtensionsType.GetMethod(nameof(StructuralEqualityExtensions.IsRefEquals));
  369.             private static readonly MethodInfo isEqualsForValueMethod = structuralEqualityExtensionsType.GetMethod(nameof(StructuralEqualityExtensions.IsValueEquals));
  370.             private static readonly MethodInfo isEqualsForCollection = structuralEqualityExtensionsType.GetMethod(nameof(StructuralEqualityExtensions.IsCollectionsEquals));
  371.             private static readonly MethodInfo isEqualsForStructure = structuralComparerType.GetMethod(nameof(StructuralComparer.AreEqual));
  372.  
  373.             public static void PutComparisonResultToStack([NotNull] Type valueType, [NotNull] GroboIL ilGenerator)
  374.             {
  375.                 if (IsCollection(valueType, out Type itemType))
  376.                 {
  377.                     ilGenerator.Call(isEqualsForCollection.MakeGenericMethod(itemType));
  378.                 }
  379.                 else if (!IsBuildInType(valueType))
  380.                 {
  381.                     ilGenerator.Call(isEqualsForStructure.MakeGenericMethod(valueType));
  382.                 }
  383.                 else if (valueType.IsNullableValueType())
  384.                 {
  385.                     ilGenerator.Call(isEqualsForNullableMethod.MakeGenericMethod(Nullable.GetUnderlyingType(valueType)));
  386.                 }
  387.                 else if (valueType.IsValueType)
  388.                 {
  389.                     ilGenerator.Call(isEqualsForValueMethod.MakeGenericMethod(valueType));
  390.                 }
  391.                 else
  392.                 {
  393.                     ilGenerator.Call(isEqualsForRefMethod.MakeGenericMethod(valueType));
  394.                 }
  395.             }
  396.  
  397.             public static void EmitReturnFalseIfNotEquals([NotNull] GroboIL ilGenerator)
  398.             {
  399.                 var @continue = ilGenerator.DefineLabel("continue");
  400.                 ilGenerator.Brtrue(@continue);
  401.                 EmitReturnBool(false, ilGenerator);
  402.                 ilGenerator.MarkLabel(@continue);
  403.             }
  404.         }
  405.  
  406.         public static class HashCode
  407.         {
  408.             private static readonly Type structuralHashCodeCalculatorType = typeof(StructuralHashCodeCalculator);
  409.  
  410.             private static readonly MethodInfo getHashCodeForNullableMethod = structuralEqualityExtensionsType.GetMethod(nameof(StructuralEqualityExtensions.GetHashCodeForNullable));
  411.             private static readonly MethodInfo getHashCodeForRefMethod = structuralEqualityExtensionsType.GetMethod(nameof(StructuralEqualityExtensions.GetHashCodeForRef));
  412.             private static readonly MethodInfo getHashCodeForCollectionMethod = structuralEqualityExtensionsType.GetMethod(nameof(StructuralEqualityExtensions.GetHashCodeForCollection));
  413.             private static readonly MethodInfo getHashCodeForValueMethod = structuralEqualityExtensionsType.GetMethod(nameof(StructuralEqualityExtensions.GetHashCodeForValue));
  414.             private static readonly MethodInfo getHashCodeForStructureMethod = structuralHashCodeCalculatorType.GetMethod(nameof(StructuralHashCodeCalculator.CalculateHashCode));
  415.  
  416.             public static void PutCalculatedHashCodeToStack([NotNull] Type propType, [NotNull] GroboIL ilGenerator)
  417.             {
  418.                 if (IsCollection(propType, out var itemType))
  419.                 {
  420.                     ilGenerator.Call(getHashCodeForCollectionMethod.MakeGenericMethod(itemType));
  421.                 }
  422.                 else if (!IsBuildInType(propType))
  423.                 {
  424.                     ilGenerator.Call(getHashCodeForStructureMethod.MakeGenericMethod(propType));
  425.                 }
  426.                 else if (propType.IsNullableValueType())
  427.                 {
  428.                     ilGenerator.Call(getHashCodeForNullableMethod.MakeGenericMethod(Nullable.GetUnderlyingType(propType)));
  429.                 }
  430.                 else if (propType.IsValueType)
  431.                 {
  432.                     ilGenerator.Call(getHashCodeForValueMethod.MakeGenericMethod(propType));
  433.                 }
  434.                 else
  435.                 {
  436.                     ilGenerator.Call(getHashCodeForRefMethod.MakeGenericMethod(propType));
  437.                 }
  438.             }
  439.         }
  440.     }
  441. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement