Skip to content

Commit a7158a8

Browse files
authored
Add more OrderBy unittests (#584)
* Add more unittests for OrderBy with IComparer * . * wip * ok * .
1 parent 530735d commit a7158a8

File tree

6 files changed

+221
-19
lines changed

6 files changed

+221
-19
lines changed

System.Linq.Dynamic.Core.sln

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWASMExample", "src-bl
108108
EndProject
109109
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.EntityFrameworkCore.DynamicLinq.EFCore6", "src\Microsoft.EntityFrameworkCore.DynamicLinq.EFCore6\Microsoft.EntityFrameworkCore.DynamicLinq.EFCore6.csproj", "{D28F6393-B56B-40A2-AF67-E8D669F42546}"
110110
EndProject
111-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp_net5.0_EF6_InMemory", "src-console\ConsoleAppEF6_InMemory\ConsoleApp_net5.0_EF6_InMemory.csproj", "{4CC563F6-5352-4A77-A8C0-DC0D77A71DBB}"
111+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp_net6.0_EF6_InMemory", "src-console\ConsoleAppEF6_InMemory\ConsoleApp_net6.0_EF6_InMemory.csproj", "{4CC563F6-5352-4A77-A8C0-DC0D77A71DBB}"
112112
EndProject
113113
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp_net6.0", "src-console\ConsoleApp_net6.0\ConsoleApp_net6.0.csproj", "{C206917D-6E90-4A31-8533-AF2DD68FF738}"
114114
EndProject

src-console/ConsoleAppEF6_InMemory/ConsoleApp_net5.0_EF6_InMemory.csproj src-console/ConsoleAppEF6_InMemory/ConsoleApp_net6.0_EF6_InMemory.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net5.0</TargetFramework>
5+
<TargetFramework>net6.0</TargetFramework>
66
</PropertyGroup>
77

88
<ItemGroup>

src/System.Linq.Dynamic.Core/DynamicClassFactory.cs

+76-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#if !(UAP10_0)
2+
using System.Collections;
23
using System.Collections.Concurrent;
34
using System.Collections.Generic;
45
using System.Diagnostics;
@@ -21,26 +22,23 @@ namespace System.Linq.Dynamic.Core
2122
/// </summary>
2223
public static class DynamicClassFactory
2324
{
24-
// EmptyTypes is used to indicate that we are looking for someting without any parameters.
25-
private static readonly Type[] EmptyTypes = new Type[0];
26-
2725
private static readonly ConcurrentDictionary<string, Type> GeneratedTypes = new ConcurrentDictionary<string, Type>();
2826

2927
private static readonly ModuleBuilder ModuleBuilder;
3028

3129
// Some objects we cache
32-
private static readonly CustomAttributeBuilder CompilerGeneratedAttributeBuilder = new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(EmptyTypes), new object[0]);
30+
private static readonly CustomAttributeBuilder CompilerGeneratedAttributeBuilder = new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes), new object[0]);
3331
private static readonly CustomAttributeBuilder DebuggerBrowsableAttributeBuilder = new CustomAttributeBuilder(typeof(DebuggerBrowsableAttribute).GetConstructor(new[] { typeof(DebuggerBrowsableState) }), new object[] { DebuggerBrowsableState.Never });
34-
private static readonly CustomAttributeBuilder DebuggerHiddenAttributeBuilder = new CustomAttributeBuilder(typeof(DebuggerHiddenAttribute).GetConstructor(EmptyTypes), new object[0]);
32+
private static readonly CustomAttributeBuilder DebuggerHiddenAttributeBuilder = new CustomAttributeBuilder(typeof(DebuggerHiddenAttribute).GetConstructor(Type.EmptyTypes), new object[0]);
3533

36-
private static readonly ConstructorInfo ObjectCtor = typeof(object).GetConstructor(EmptyTypes);
34+
private static readonly ConstructorInfo ObjectCtor = typeof(object).GetConstructor(Type.EmptyTypes);
3735
#if WINDOWS_APP || UAP10_0 || NETSTANDARD
3836
private static readonly MethodInfo ObjectToString = typeof(object).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public);
3937
#else
40-
private static readonly MethodInfo ObjectToString = typeof(object).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, EmptyTypes, null);
38+
private static readonly MethodInfo ObjectToString = typeof(object).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null);
4139
#endif
4240

43-
private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(EmptyTypes);
41+
private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes);
4442
#if WINDOWS_APP || UAP10_0 || NETSTANDARD
4543
private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod("Append", new[] { typeof(string) });
4644
private static readonly MethodInfo StringBuilderAppendObject = typeof(StringBuilder).GetMethod("Append", new[] { typeof(object) });
@@ -56,7 +54,7 @@ public static class DynamicClassFactory
5654
private static readonly MethodInfo EqualityComparerEquals = EqualityComparer.GetMethod("Equals", new[] { EqualityComparerGenericArgument, EqualityComparerGenericArgument });
5755
private static readonly MethodInfo EqualityComparerGetHashCode = EqualityComparer.GetMethod("GetHashCode", new[] { EqualityComparerGenericArgument });
5856
#else
59-
private static readonly MethodInfo EqualityComparerDefault = EqualityComparer.GetMethod("get_Default", BindingFlags.Static | BindingFlags.Public, null, EmptyTypes, null);
57+
private static readonly MethodInfo EqualityComparerDefault = EqualityComparer.GetMethod("get_Default", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null);
6058
private static readonly MethodInfo EqualityComparerEquals = EqualityComparer.GetMethod("Equals", BindingFlags.Instance | BindingFlags.Public, null, new[] { EqualityComparerGenericArgument, EqualityComparerGenericArgument }, null);
6159
private static readonly MethodInfo EqualityComparerGetHashCode = EqualityComparer.GetMethod("GetHashCode", BindingFlags.Instance | BindingFlags.Public, null, new[] { EqualityComparerGenericArgument }, null);
6260
#endif
@@ -85,6 +83,71 @@ static DynamicClassFactory()
8583
ModuleBuilder = assemblyBuilder.DefineDynamicModule(DynamicModuleName);
8684
}
8785

86+
/// <summary>
87+
/// Create a GenericComparerType based on the GenericType and an instance of a <see cref="IComparer"/>.
88+
/// </summary>
89+
/// <param name="comparerGenericType">The GenericType</param>
90+
/// <param name="comparerType">The <see cref="IComparer"/> instance</param>
91+
/// <returns>Type</returns>
92+
public static Type CreateGenericComparerType(Type comparerGenericType, Type comparerType)
93+
{
94+
Check.NotNull(comparerGenericType, nameof(comparerGenericType));
95+
Check.NotNull(comparerType, nameof(comparerType));
96+
97+
var key = $"{comparerGenericType.FullName}_{comparerType.FullName}";
98+
99+
if (!GeneratedTypes.TryGetValue(key, out var type))
100+
{
101+
// We create only a single class at a time, through this lock
102+
// Note that this is a variant of the double-checked locking.
103+
// It is safe because we are using a thread safe class.
104+
lock (GeneratedTypes)
105+
{
106+
if (!GeneratedTypes.TryGetValue(key, out type))
107+
{
108+
var compareMethodGeneric = comparerGenericType.GetMethod("Compare");
109+
var compareMethod = typeof(IComparer).GetMethod("Compare");
110+
var compareCtor = comparerType.GetConstructor(Type.EmptyTypes);
111+
112+
var typeBuilder = ModuleBuilder.DefineType(key, TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoLayout, typeof(object));
113+
typeBuilder.AddInterfaceImplementation(comparerGenericType);
114+
115+
var fieldBuilder = typeBuilder.DefineField("_c", typeof(IComparer), FieldAttributes.Private | FieldAttributes.InitOnly);
116+
117+
var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, Type.EmptyTypes);
118+
var constructorIL = constructorBuilder.GetILGenerator();
119+
constructorIL.Emit(OpCodes.Ldarg_0);
120+
constructorIL.Emit(OpCodes.Call, ObjectCtor);
121+
constructorIL.Emit(OpCodes.Ldarg_0);
122+
constructorIL.Emit(OpCodes.Newobj, compareCtor);
123+
constructorIL.Emit(OpCodes.Stfld, fieldBuilder);
124+
constructorIL.Emit(OpCodes.Ret);
125+
126+
var methodBuilder = typeBuilder.DefineMethod(
127+
compareMethodGeneric.Name,
128+
compareMethodGeneric.Attributes & ~MethodAttributes.Abstract,
129+
compareMethodGeneric.CallingConvention,
130+
compareMethodGeneric.ReturnType,
131+
compareMethodGeneric.GetParameters().Select(p => p.ParameterType).ToArray()
132+
);
133+
var methodBuilderIL = methodBuilder.GetILGenerator();
134+
methodBuilderIL.Emit(OpCodes.Ldarg_0);
135+
methodBuilderIL.Emit(OpCodes.Ldfld, fieldBuilder);
136+
methodBuilderIL.Emit(OpCodes.Ldarg_1);
137+
methodBuilderIL.Emit(OpCodes.Box, typeof(int));
138+
methodBuilderIL.Emit(OpCodes.Ldarg_2);
139+
methodBuilderIL.Emit(OpCodes.Box, typeof(int));
140+
methodBuilderIL.Emit(OpCodes.Callvirt, compareMethod);
141+
methodBuilderIL.Emit(OpCodes.Ret);
142+
143+
return GeneratedTypes.GetOrAdd(key, typeBuilder.CreateType());
144+
}
145+
}
146+
}
147+
148+
return type;
149+
}
150+
88151
/// <summary>
89152
/// The CreateType method creates a new data class with a given set of public properties and returns the System.Type object for the newly created class. If a data class with an identical sequence of properties has already been created, the System.Type object for this class is returned.
90153
/// Data classes implement private instance variables and read/write property accessors for the specified properties.Data classes also override the Equals and GetHashCode members to implement by-value equality.
@@ -157,7 +220,7 @@ public static Type CreateType([NotNull] IList<DynamicProperty> properties, bool
157220
fields[i] = tb.DefineField($"<{names[i]}>i__Field", generics[i].AsType(), FieldAttributes.Private | FieldAttributes.InitOnly);
158221
fields[i].SetCustomAttribute(DebuggerBrowsableAttributeBuilder);
159222

160-
PropertyBuilder property = tb.DefineProperty(names[i], PropertyAttributes.None, CallingConventions.HasThis, generics[i].AsType(), EmptyTypes);
223+
PropertyBuilder property = tb.DefineProperty(names[i], PropertyAttributes.None, CallingConventions.HasThis, generics[i].AsType(), Type.EmptyTypes);
161224

162225
// getter
163226
MethodBuilder getter = tb.DefineMethod($"get_{names[i]}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, CallingConventions.HasThis, generics[i].AsType(), null);
@@ -184,7 +247,7 @@ public static Type CreateType([NotNull] IList<DynamicProperty> properties, bool
184247
}
185248

186249
// ToString()
187-
MethodBuilder toString = tb.DefineMethod("ToString", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(string), EmptyTypes);
250+
MethodBuilder toString = tb.DefineMethod("ToString", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(string), Type.EmptyTypes);
188251
toString.SetCustomAttribute(DebuggerHiddenAttributeBuilder);
189252
ILGenerator ilgeneratorToString = toString.GetILGenerator();
190253
ilgeneratorToString.DeclareLocal(typeof(StringBuilder));
@@ -206,7 +269,7 @@ public static Type CreateType([NotNull] IList<DynamicProperty> properties, bool
206269
Label equalsLabel = ilgeneratorEquals.DefineLabel();
207270

208271
// GetHashCode()
209-
MethodBuilder getHashCode = tb.DefineMethod("GetHashCode", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(int), EmptyTypes);
272+
MethodBuilder getHashCode = tb.DefineMethod("GetHashCode", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(int), Type.EmptyTypes);
210273
getHashCode.SetCustomAttribute(DebuggerHiddenAttributeBuilder);
211274
ILGenerator ilgeneratorGetHashCode = getHashCode.GetILGenerator();
212275
ilgeneratorGetHashCode.DeclareLocal(typeof(int));
@@ -283,7 +346,7 @@ public static Type CreateType([NotNull] IList<DynamicProperty> properties, bool
283346
if (createParameterCtor && names.Any())
284347
{
285348
// .ctor default
286-
ConstructorBuilder constructorDef = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, EmptyTypes);
349+
ConstructorBuilder constructorDef = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, Type.EmptyTypes);
287350
constructorDef.SetCustomAttribute(DebuggerHiddenAttributeBuilder);
288351

289352
ILGenerator ilgeneratorConstructorDef = constructorDef.GetILGenerator();

src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

+23-2
Original file line numberDiff line numberDiff line change
@@ -1564,6 +1564,11 @@ public static IOrderedQueryable<TSource> OrderBy<TSource>([NotNull] this IQuerya
15641564
/// </example>
15651565
public static IOrderedQueryable OrderBy([NotNull] this IQueryable source, [NotNull] ParsingConfig config, [NotNull] string ordering, params object[] args)
15661566
{
1567+
if (args.Length > 0 && args[0] != null && args[0].GetType().GetInterfaces().Any(i => i.Name.Contains("IComparer`1")))
1568+
{
1569+
return InternalOrderBy(source, ParsingConfig.Default, ordering, args[0], args);
1570+
}
1571+
15671572
return InternalOrderBy(source, config, ordering, null, args);
15681573
}
15691574

@@ -1581,7 +1586,7 @@ public static IOrderedQueryable OrderBy([NotNull] this IQueryable source, [NotNu
15811586
return InternalOrderBy(source, config, ordering, comparer, args);
15821587
}
15831588

1584-
internal static IOrderedQueryable InternalOrderBy([NotNull] IQueryable source, [NotNull] ParsingConfig config, [NotNull] string ordering, IComparer comparer, params object[] args)
1589+
internal static IOrderedQueryable InternalOrderBy([NotNull] IQueryable source, [NotNull] ParsingConfig config, [NotNull] string ordering, object comparer, params object[] args)
15851590
{
15861591
Check.NotNull(source, nameof(source));
15871592
Check.NotNull(config, nameof(config));
@@ -1605,11 +1610,27 @@ internal static IOrderedQueryable InternalOrderBy([NotNull] IQueryable source, [
16051610
else
16061611
{
16071612
var comparerGenericType = typeof(IComparer<>).MakeGenericType(dynamicOrdering.Selector.Type);
1613+
1614+
ConstantExpression constant;
1615+
if (comparerGenericType.IsInstanceOfType(comparer))
1616+
{
1617+
constant = Expression.Constant(comparer, comparerGenericType);
1618+
}
1619+
else
1620+
{
1621+
#if !UAP10_0
1622+
var newType = DynamicClassFactory.CreateGenericComparerType(comparerGenericType, comparer.GetType());
1623+
constant = Expression.Constant(Activator.CreateInstance(newType), comparerGenericType);
1624+
#else
1625+
constant = Expression.Constant(comparer, comparerGenericType);
1626+
#endif
1627+
}
1628+
16081629
queryExpr = Expression.Call(
16091630
typeof(Queryable), dynamicOrdering.MethodName,
16101631
new[] { source.ElementType, dynamicOrdering.Selector.Type },
16111632
queryExpr, Expression.Quote(Expression.Lambda(dynamicOrdering.Selector, parameters)),
1612-
Expression.Constant(comparer, comparerGenericType));
1633+
constant);
16131634
}
16141635
}
16151636

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace System.Linq.Dynamic.Core.Tests
7+
{
8+
public class DynamicClassFactoryTests
9+
{
10+
[Fact]
11+
public void CreateGenericComparerType()
12+
{
13+
// Assign
14+
var comparer = new IntComparer();
15+
var comparerGenericType = typeof(IComparer<>).MakeGenericType(typeof(int));
16+
17+
// Act
18+
var type = DynamicClassFactory.CreateGenericComparerType(comparerGenericType, comparer.GetType());
19+
20+
// Assert
21+
var instance = (IComparer<int>)Activator.CreateInstance(type);
22+
int greaterThan = instance.Compare(1, 2);
23+
greaterThan.Should().Be(1);
24+
25+
int equal = instance.Compare(1, 1);
26+
equal.Should().Be(0);
27+
28+
int lessThan = instance.Compare(2, 1);
29+
lessThan.Should().Be(-1);
30+
}
31+
}
32+
33+
public class IntComparer : IComparer
34+
{
35+
public int Compare(object x, object y)
36+
{
37+
return new CaseInsensitiveComparer().Compare(y, x);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)