Exemplo n.º 1
0
            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, &amp; 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 &amp; 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>)));
            }