public static Func <T, T, int> BuildComparerFunc(SortCriteria sortCriteria) { string sortField = sortCriteria.SortField; if (!SortComparerFactory <T> .s_ComparerCache.TryGetValue(sortField, out Func <T, T, int> comparerFunc)) { comparerFunc = (x, y) => { object?valueX = SortComparerReflectionHelper.FindPropertyGetMethod(x !.GetType(), sortField)?.Invoke(x, null); object?valueY = SortComparerReflectionHelper.FindPropertyGetMethod(y !.GetType(), sortField)?.Invoke(y, null); if (!TryEnsureValidReferences(valueX, valueY, out int referenceComparisonResult)) { return(referenceComparisonResult); } Type targetType = SortComparerReflectionHelper.ResolveTargetType(valueX.GetType()); if (targetType == SortComparerReflectionHelper.ResolveTargetType(valueY.GetType())) { MethodInfo?compareToMethodInfo = SortComparerReflectionHelper.FindCompareToMethodInfo(targetType); if (compareToMethodInfo != null) { return((int)compareToMethodInfo.Invoke(valueX, new object[] { valueY })); } } return(Comparer <object> .Default.Compare(valueX, valueY)); }; SortComparerFactory <T> .s_ComparerCache.TryAdd(sortField, comparerFunc); } return(comparerFunc); }
internal static Func <T, T, int> BuildPropertyComparer(Type typeX, Type typeY, string sortField) { MethodInfo?propertyX = SortComparerReflectionHelper.FindPropertyGetMethod(typeX, sortField); MethodInfo?propertyY = SortComparerReflectionHelper.FindPropertyGetMethod(typeY, sortField); if (propertyX == null && propertyY == null) { return((x, y) => 0); } if (propertyX == null || propertyY == null) { return(BuildOneSidedComparer((propertyX ?? propertyY) !, propertyX != null, sortField)); } Type targetType = SortComparerReflectionHelper.ResolveTargetType(propertyX.ReturnType); if (targetType != SortComparerReflectionHelper.ResolveTargetType(propertyY.ReturnType)) { return(BuildDefaultComparer(propertyX, propertyY, sortField)); } bool isEnum = targetType.IsEnum; if (isEnum) { targetType = typeof(long); } MethodInfo?compareToMethodInfo = SortComparerReflectionHelper.FindCompareToMethodInfo(targetType); return(compareToMethodInfo == null ? BuildDefaultComparer(propertyX, propertyY, sortField) : !targetType.IsValueType ? BuildReferenceTypePropertyComparer(propertyX, propertyY, sortField, targetType, compareToMethodInfo) : BuildValueTypePropertyComparer(propertyX, propertyY, sortField, typeof(Nullable <>).MakeGenericType(targetType), targetType, compareToMethodInfo, isEnum)); }
/// <remarks> /// Compare two value types. For example, int to int. Mixed nullable is allowed, for example int? to int. Comparison is always done using <see cref="Nullable{T}"/>. /// Enums are converted to long and compared that way. /// Built using MethodsToDecompile.CompareValueType, MethodsToDecompile.CompareValueBaseType, MethodsToDecompile.CompareValueTypeNoGetter, & MethodsToDecompile.CompareEnumType. /// </remarks> private static Func <T, T, int> BuildValueTypePropertyComparer( MethodInfo?propertyX, MethodInfo?propertyY, string sortField, Type targetNullableType, Type targetUnderlyingType, MethodInfo?compareToMethod, bool isEnum) { SortComparerReflectionHelper.NullableTypeInfo nullableTypeInfo = SortComparerReflectionHelper.FindNullableTypeInfo(targetNullableType, targetUnderlyingType); DynamicMethod compareMethod = new DynamicMethod($"{propertyX?.ReturnType.Name}.{propertyY?.ReturnType.Name}.{sortField}$Comparer", typeof(int), new[] { s_SourceType, s_SourceType }, true); ILGenerator generator = compareMethod.GetILGenerator(); Label executeComparisonLabel = generator.DefineLabel(); /* * .locals init ( * [0] valuetype [netstandard]System.Nullable`1<int32> valueX, <- Nullable<valueType> * [1] valuetype [netstandard]System.Nullable`1<int32> valueY, <- Nullable<valueType> * [2] int32 refCheck, * [3] int32 <- valueType * ) */ generator.DeclareLocal(targetNullableType); generator.DeclareLocal(targetNullableType); generator.DeclareLocal(typeof(int)); generator.DeclareLocal(targetUnderlyingType); ReadValueTypeIntoFirstLocal(propertyX, true, targetNullableType, isEnum, nullableTypeInfo.TargetNullableTypeCtor, generator); if (propertyY == null) { /* * // int? valueY = null; * ldloca.s 1 * initobj valuetype [netstandard]System.Nullable`1<int32> */ generator.Emit(OpCodes.Ldloca_S, 1); generator.Emit(OpCodes.Initobj, targetNullableType); } else if (!isEnum && propertyY.ReturnType == targetNullableType) { /* * // int? valueY = ((SpaceThing)y).License; * ldarg.1 * castclass Code.SpaceThing <-- cast to derived type if property doesn't exist on base * callvirt instance valuetype [netstandard]System.Nullable`1<int32> Code.SpaceThing::get_License() * stloc.1 */ generator.Emit(OpCodes.Ldarg_1); if (propertyY.DeclaringType != s_SourceType) { generator.Emit(OpCodes.Castclass, propertyY.DeclaringType); } generator.Emit(OpCodes.Callvirt, propertyY); generator.Emit(OpCodes.Stloc_1); } else { /* * // int? valueY = ((SwampThing)y).License; * ldloca.s 1 * ldarg.1 * castclass Code.SwampThing * callvirt instance int32 Code.SwampThing::get_License() * conv.i8 <- cast enums to long * call instance void valuetype [netstandard]System.Nullable`1<int32>::.ctor(!0) */ generator.Emit(OpCodes.Ldloca_S, 1); generator.Emit(OpCodes.Ldarg_1); if (propertyY.DeclaringType != s_SourceType) { generator.Emit(OpCodes.Castclass, propertyY.DeclaringType); } generator.Emit(OpCodes.Callvirt, propertyY); if (isEnum) { generator.Emit(OpCodes.Conv_I8); } generator.Emit(OpCodes.Call, nullableTypeInfo.TargetNullableTypeCtor); } /* * // if (!ReflectionHelper.TryEnsureValidValues(valueX.HasValue, valueY.HasValue, out int comparisonResult)) * ldloca.s 0 * call instance bool valuetype [netstandard]System.Nullable`1<int32>::get_HasValue() * ldloca.s 1 * call instance bool valuetype [netstandard]System.Nullable`1<int32>::get_HasValue() * ldloca.s 2 * call bool Code.ReflectionHelper::TryEnsureValidValues(bool, bool, int32&) * brtrue.s executeComparisonLabel * * // return comparisonResult; * ldloc.2 * ret */ generator.Emit(OpCodes.Ldloca_S, 0); generator.Emit(OpCodes.Call, nullableTypeInfo.TargetNullableTypeHasValue); generator.Emit(OpCodes.Ldloca_S, 1); generator.Emit(OpCodes.Call, nullableTypeInfo.TargetNullableTypeHasValue); generator.Emit(OpCodes.Ldloca_S, 2); generator.Emit(OpCodes.Call, SortComparerReflectionHelper.TryEnsureValidValuesMethodInfo); generator.Emit(OpCodes.Brtrue_S, executeComparisonLabel); generator.Emit(OpCodes.Ldloc_2); generator.Emit(OpCodes.Ret); generator.MarkLabel(executeComparisonLabel); /* * // return num.Value.CompareTo(license.Value); * ldloca.s 0 * call instance !0 valuetype [netstandard]System.Nullable`1<int32>::get_Value() * stloc.3 * ldloca.s 3 * ldloca.s 1 * call instance !0 valuetype [netstandard]System.Nullable`1<int32>::get_Value() * call instance int32 [netstandard]System.Int32::CompareTo(int32) * * ret */ generator.Emit(OpCodes.Ldloca_S, 0); generator.Emit(OpCodes.Call, nullableTypeInfo.TargetNullableTypeValue); generator.Emit(OpCodes.Stloc_3); generator.Emit(OpCodes.Ldloca_S, 3); generator.Emit(OpCodes.Ldloca_S, 1); generator.Emit(OpCodes.Call, nullableTypeInfo.TargetNullableTypeValue); generator.Emit(OpCodes.Call, compareToMethod); generator.Emit(OpCodes.Ret); return((Func <T, T, int>)compareMethod.CreateDelegate(typeof(Func <T, T, int>))); }
/// <remarks> /// This is used when a property is defined on one side of the comparison but not the other. /// Case A: PropertyX == null && PropertyY != null /// Case B: PropertyX != null && PropertyY == null /// The goal here is to return 0 if the found property is null or !HasValue, otherwise return -1 for CaseA, 1 for CaseB. /// Built using MethodsToDecompile.DefaultOneSidedReferenceComparer & MethodsToDecompile.DefaultOneSidedValueComparer. /// </remarks> private static Func <T, T, int> BuildOneSidedComparer( MethodInfo property, bool isPropertyX, string sortField) { DynamicMethod compareMethod = new DynamicMethod($"{property.ReturnType.Name}.{isPropertyX}.{sortField}$OneSideComparer", typeof(int), new[] { s_SourceType, s_SourceType }, true); ILGenerator generator = compareMethod.GetILGenerator(); Label returnNonNullResult = generator.DefineLabel(); Type targetUnderlyingType = SortComparerReflectionHelper.ResolveTargetType(property.ReturnType); if (property.ReturnType.IsValueType) { Type targetNullableType = typeof(Nullable <>).MakeGenericType(targetUnderlyingType); SortComparerReflectionHelper.NullableTypeInfo nullableTypeInfo = SortComparerReflectionHelper.FindNullableTypeInfo(targetNullableType, targetUnderlyingType); /* * .locals init ( * [0] valuetype [netstandard]System.Nullable`1<int32> valueX * ) */ generator.DeclareLocal(targetUnderlyingType); /* * // if (!new int?(x.Id).HasValue) */ ReadValueTypeIntoFirstLocal(property, isPropertyX, targetNullableType, false, nullableTypeInfo.TargetNullableTypeCtor, generator); /* * ldloca.s 0 * call instance bool valuetype [netstandard]System.Nullable`1<int32>::get_HasValue() * brtrue.s returnNonNullResult */ generator.Emit(OpCodes.Ldloca_S, 0); generator.Emit(OpCodes.Call, nullableTypeInfo.TargetNullableTypeHasValue); generator.Emit(OpCodes.Brtrue_S, returnNonNullResult); /* * // return 0; * ldc.i4.0 * ret */ generator.Emit(OpCodes.Ldc_I4_0); generator.Emit(OpCodes.Ret); generator.MarkLabel(returnNonNullResult); /* * // return 1; * ldc.i4.1 <- return -1 if !isPropertyX */ if (isPropertyX) { generator.Emit(OpCodes.Ldc_I4_1); } else { generator.Emit(OpCodes.Ldc_I4_M1); } } else { /* * // if (x.Text == null) * ldarg.0 * callvirt instance string Code.Thing::get_Text() * brtrue.s returnNonNullResult */ if (isPropertyX) { generator.Emit(OpCodes.Ldarg_0); } else { generator.Emit(OpCodes.Ldarg_1); } if (property.DeclaringType != s_SourceType) { generator.Emit(OpCodes.Castclass, property.DeclaringType); } generator.Emit(OpCodes.Callvirt, property); generator.Emit(OpCodes.Brtrue_S, returnNonNullResult); /* * // return 0; * ldc.i4.0 * ret */ generator.Emit(OpCodes.Ldc_I4_0); generator.Emit(OpCodes.Ret); generator.MarkLabel(returnNonNullResult); /* * // return -1; <-- return 1 if isPropertyX * ldc.i4.m1 */ if (isPropertyX) { generator.Emit(OpCodes.Ldc_I4_1); } else { generator.Emit(OpCodes.Ldc_I4_M1); } } generator.Emit(OpCodes.Ret); return((Func <T, T, int>)compareMethod.CreateDelegate(typeof(Func <T, T, int>))); }