Skip to content

Commit d637ed4

Browse files
Add support to extension methods (very basic case only)
1 parent 016fd51 commit d637ed4

File tree

11 files changed

+270
-52
lines changed

11 files changed

+270
-52
lines changed

Z.Dynamic.Core.Lab/Request_DynamicLinqType.cs

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,47 @@
1010

1111
namespace Z.Dynamic.Core.Lab
1212
{
13-
public class Request_DynamicLinqType
13+
[DynamicLinqType]
14+
public static class Utils
1415
{
16+
public static int IncrementMe(this int values)
17+
{
18+
return values + 1;
19+
}
1520

16-
[DynamicLinqType]
17-
public static class Utils
21+
public static int IncrementMe(this int values, int y)
1822
{
19-
public static string[] ConvertToArray(params string[] values)
20-
{
21-
if (values == null)
22-
{
23-
return new string[0];
24-
}
23+
return values + y;
24+
}
2525

26-
return values.ToArray();
26+
public static string[] ConvertToArray(params string[] values)
27+
{
28+
if (values == null)
29+
{
30+
return new string[0];
2731
}
2832

29-
public static string[] ConvertToArray(int a, params string[] values)
30-
{
31-
if (values == null)
32-
{
33-
return null;
34-
}
33+
return values.ToArray();
34+
}
3535

36-
return values.ToArray();
36+
public static string[] ConvertToArray(int a, params string[] values)
37+
{
38+
if (values == null)
39+
{
40+
return null;
3741
}
42+
43+
return values.ToArray();
3844
}
3945

46+
47+
}
48+
49+
public class Request_DynamicLinqType
50+
{
51+
52+
53+
4054
public static void Execute()
4155
{
4256
var externals = new Dictionary<string, object>
@@ -54,13 +68,18 @@ public static void Execute()
5468
//var result = del.DynamicInvoke();
5569

5670
var config = new ParsingConfig();
57-
58-
var list = new[] { new X { }, new X { Values = new[] { "a", "b" } } }.AsQueryable();
59-
var result = list.Select("Utils.ConvertToArray(Values)").ToDynamicList<string[]>();
71+
72+
//var list = new[] { new X { }, new X { Values = new[] { "a", "b" } } }.AsQueryable();
73+
//var result = list.Select("Utils.ConvertToArray(Values)").ToDynamicList<string[]>();
74+
75+
76+
var list = new[] { new X { Test = 1}, new X { Test = 2}}.AsQueryable();
77+
var result = list.Select("Test.IncrementMe(5)").ToDynamicList<int>();
6078
}
6179

6280
public class X
6381
{
82+
public int Test { get; set; }
6483
public string[] Values { get; set; }
6584
}
6685
}

src-console/ConsoleAppEF2.0.2_InMemory/Program.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using System.Linq;
77
using System.Linq.Dynamic.Core;
88
using System.Linq.Dynamic.Core.CustomTypeProviders;
9+
using System.Reflection;
10+
using System.Runtime.CompilerServices;
911

1012
namespace ConsoleAppEF2
1113
{
@@ -35,6 +37,23 @@ public HashSet<Type> GetCustomTypes()
3537
return set;
3638
}
3739

40+
public Dictionary<Type, List<MethodInfo>> GetExtensionMethods()
41+
{
42+
var types = GetCustomTypes();
43+
44+
List<Tuple<Type, MethodInfo>> list = new List<Tuple<Type, MethodInfo>>();
45+
46+
foreach (var type in types)
47+
{
48+
var extensionMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
49+
.Where(x => x.IsDefined(typeof(ExtensionAttribute), false)).ToList();
50+
51+
extensionMethods.ForEach(x => list.Add(new Tuple<Type, MethodInfo>(x.GetParameters()[0].ParameterType, x)));
52+
}
53+
54+
return list.GroupBy(x => x.Item1, tuple => tuple.Item2).ToDictionary(key => key.Key, methods => methods.ToList());
55+
}
56+
3857
public Type ResolveType(string typeName)
3958
{
4059
var assemblies = AppDomain.CurrentDomain.GetAssemblies();

src-console/ConsoleAppEF2.0/Program.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Linq;
44
using System.Linq.Dynamic.Core;
55
using System.Linq.Dynamic.Core.CustomTypeProviders;
6+
using System.Reflection;
7+
using System.Runtime.CompilerServices;
68
using ConsoleAppEF2.Database;
79
using JetBrains.Annotations;
810
using Microsoft.EntityFrameworkCore;
@@ -25,6 +27,23 @@ public HashSet<Type> GetCustomTypes()
2527
return set;
2628
}
2729

30+
public Dictionary<Type, List<MethodInfo>> GetExtensionMethods()
31+
{
32+
var types = GetCustomTypes();
33+
34+
List<Tuple<Type, MethodInfo>> list = new List<Tuple<Type, MethodInfo>>();
35+
36+
foreach (var type in types)
37+
{
38+
var extensionMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
39+
.Where(x => x.IsDefined(typeof(ExtensionAttribute), false)).ToList();
40+
41+
extensionMethods.ForEach(x => list.Add(new Tuple<Type, MethodInfo>(x.GetParameters()[0].ParameterType, x)));
42+
}
43+
44+
return list.GroupBy(x => x.Item1, tuple => tuple.Item2).ToDictionary(key => key.Key, methods => methods.ToList());
45+
}
46+
2847
public Type ResolveType(string typeName)
2948
{
3049
var assemblies = AppDomain.CurrentDomain.GetAssemblies();

src-console/ConsoleAppEF2.1.1/Program.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Linq;
44
using System.Linq.Dynamic.Core;
55
using System.Linq.Dynamic.Core.CustomTypeProviders;
6+
using System.Reflection;
7+
using System.Runtime.CompilerServices;
68
using ConsoleAppEF2.Database;
79
using JetBrains.Annotations;
810
using Microsoft.EntityFrameworkCore;
@@ -25,6 +27,23 @@ public HashSet<Type> GetCustomTypes()
2527
return set;
2628
}
2729

30+
public Dictionary<Type, List<MethodInfo>> GetExtensionMethods()
31+
{
32+
var types = GetCustomTypes();
33+
34+
List<Tuple<Type, MethodInfo>> list = new List<Tuple<Type, MethodInfo>>();
35+
36+
foreach (var type in types)
37+
{
38+
var extensionMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
39+
.Where(x => x.IsDefined(typeof(ExtensionAttribute), false)).ToList();
40+
41+
extensionMethods.ForEach(x => list.Add(new Tuple<Type, MethodInfo>(x.GetParameters()[0].ParameterType, x)));
42+
}
43+
44+
return list.GroupBy(x => x.Item1, tuple => tuple.Item2).ToDictionary(key => key.Key, methods => methods.ToList());
45+
}
46+
2847
public Type ResolveType(string typeName)
2948
{
3049
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#if NET35
2+
namespace System
3+
{
4+
public class Tuple<T1, T2>
5+
{
6+
public T1 Item1 { get; private set; }
7+
public T2 Item2 { get; private set; }
8+
9+
internal Tuple(T1 item1, T2 item2)
10+
{
11+
Item1 = item1;
12+
Item2 = item2;
13+
}
14+
}
15+
}
16+
17+
#endif

src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.Linq.Dynamic.Core.Validation;
33
using System.Reflection;
4+
using System.Runtime.CompilerServices;
45

56
namespace System.Linq.Dynamic.Core.CustomTypeProviders
67
{
@@ -19,11 +20,12 @@ public class DefaultDynamicLinqCustomTypeProvider : AbstractDynamicLinqCustomTyp
1920
private readonly bool _cacheCustomTypes;
2021

2122
private HashSet<Type> _cachedCustomTypes;
23+
private Dictionary<Type, List<MethodInfo>> _cachedExtensionMethods;
2224

2325
/// <summary>
2426
/// Initializes a new instance of the <see cref="DefaultDynamicLinqCustomTypeProvider"/> class.
2527
/// </summary>
26-
/// <param name="cacheCustomTypes">Defines whether to cache the CustomTypes which are found in the Application Domain. Default set to 'true'.</param>
28+
/// <param name="cacheCustomTypes">Defines whether to cache the CustomTypes (including extension methods) which are found in the Application Domain. Default set to 'true'.</param>
2729
public DefaultDynamicLinqCustomTypeProvider(bool cacheCustomTypes = true)
2830
{
2931
_cacheCustomTypes = cacheCustomTypes;
@@ -45,6 +47,22 @@ public virtual HashSet<Type> GetCustomTypes()
4547
return GetCustomTypesInternal();
4648
}
4749

50+
/// <inheritdoc cref="IDynamicLinkCustomTypeProvider.GetExtensionMethods"/>
51+
public Dictionary<Type, List<MethodInfo>> GetExtensionMethods()
52+
{
53+
if (_cacheCustomTypes)
54+
{
55+
if (_cachedExtensionMethods == null)
56+
{
57+
_cachedExtensionMethods = GetExtensionMethodsInternal();
58+
}
59+
60+
return _cachedExtensionMethods;
61+
}
62+
63+
return GetExtensionMethodsInternal();
64+
}
65+
4866
/// <inheritdoc cref="IDynamicLinkCustomTypeProvider.ResolveType"/>
4967
public Type ResolveType(string typeName)
5068
{
@@ -68,5 +86,22 @@ private HashSet<Type> GetCustomTypesInternal()
6886
IEnumerable<Assembly> assemblies = _assemblyHelper.GetAssemblies();
6987
return new HashSet<Type>(FindTypesMarkedWithDynamicLinqTypeAttribute(assemblies));
7088
}
89+
90+
private Dictionary<Type, List<MethodInfo>> GetExtensionMethodsInternal()
91+
{
92+
var types = GetCustomTypes();
93+
94+
List<Tuple<Type, MethodInfo>> list= new List<Tuple<Type, MethodInfo>>();
95+
96+
foreach (var type in types)
97+
{
98+
var extensionMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
99+
.Where(x => x.IsDefined(typeof(ExtensionAttribute), false)).ToList();
100+
101+
extensionMethods.ForEach(x => list.Add(new Tuple<Type, MethodInfo>(x.GetParameters()[0].ParameterType, x)));
102+
}
103+
104+
return list.GroupBy(x => x.Item1, tuple => tuple.Item2).ToDictionary(key => key.Key, methods => methods.ToList());
105+
}
71106
}
72107
}

src/System.Linq.Dynamic.Core/CustomTypeProviders/IDynamicLinkCustomTypeProvider.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using JetBrains.Annotations;
22
using System.Collections.Generic;
3+
using System.Reflection;
34

45
namespace System.Linq.Dynamic.Core.CustomTypeProviders
56
{
@@ -14,6 +15,12 @@ public interface IDynamicLinkCustomTypeProvider
1415
/// <returns>A <see cref="HashSet{Type}" /> list of custom types.</returns>
1516
HashSet<Type> GetCustomTypes();
1617

18+
/// <summary>
19+
/// Returns a list of custom extension methods that System.Linq.Dynamic.Core will understand.
20+
/// </summary>
21+
/// <returns>A list of custom extension methods that System.Linq.Dynamic.Core will understand.</returns>
22+
Dictionary<Type, List<MethodInfo>> GetExtensionMethods();
23+
1724
/// <summary>
1825
/// Resolve any type by fullname which is registered in the current application domain.
1926
/// </summary>

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,8 @@ Expression ParseIn()
350350

351351
var args = new[] { left };
352352

353-
if (_methodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, ref args, out MethodBase containsSignature) != 1)
353+
Expression nullExpressionReference = null;
354+
if (_methodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, ref nullExpressionReference, ref args, out MethodBase containsSignature) != 1)
354355
{
355356
throw ParseError(op.Pos, Res.NoApplicableAggregate, nameof(IEnumerableSignatures.Contains), string.Join(",", args.Select(a => a.Type.Name).ToArray()));
356357
}
@@ -1476,7 +1477,9 @@ Expression ParseLambdaInvocation(LambdaExpression lambda)
14761477
int errorPos = _textParser.CurrentToken.Pos;
14771478
_textParser.NextToken();
14781479
Expression[] args = ParseArgumentList();
1479-
if (_methodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, ref args, out MethodBase _) != 1)
1480+
1481+
Expression nullExpressionReference = null;
1482+
if (_methodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, ref nullExpressionReference, ref args, out MethodBase _) != 1)
14801483
{
14811484
throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda);
14821485
}
@@ -1625,7 +1628,7 @@ Expression ParseMemberAccess(Type type, Expression instance)
16251628
}
16261629

16271630
Expression[] args = ParseArgumentList();
1628-
switch (_methodFinder.FindMethod(type, id, instance == null, ref args, out MethodBase mb))
1631+
switch (_methodFinder.FindMethod(type, id, instance == null, ref instance, ref args, out MethodBase mb))
16291632
{
16301633
case 0:
16311634
throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type));
@@ -1642,7 +1645,14 @@ Expression ParseMemberAccess(Type type, Expression instance)
16421645
throw ParseError(errorPos, Res.MethodIsVoid, id, TypeHelper.GetTypeName(method.DeclaringType));
16431646
}
16441647

1645-
return Expression.Call(instance, method, args);
1648+
if (instance == null)
1649+
{
1650+
return Expression.Call(null, method, args);
1651+
}
1652+
else
1653+
{
1654+
return Expression.Call(instance, method, args);
1655+
}
16461656

16471657
default:
16481658
throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, TypeHelper.GetTypeName(type));
@@ -1743,19 +1753,19 @@ Expression ParseEnumerable(Expression instance, Type elementType, string methodN
17431753
_it = outerIt;
17441754
_parent = oldParent;
17451755

1746-
if (isDictionary && _methodFinder.ContainsMethod(typeof(IDictionarySignatures), methodName, false, ref args))
1756+
if (isDictionary && _methodFinder.ContainsMethod(typeof(IDictionarySignatures), methodName, false, null, ref args))
17471757
{
17481758
var method = type.GetMethod(methodName);
17491759
return Expression.Call(instance, method, args);
17501760
}
17511761

1752-
if (!_methodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, ref args))
1762+
if (!_methodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, null, ref args))
17531763
{
17541764
throw ParseError(errorPos, Res.NoApplicableAggregate, methodName, string.Join(",", args.Select(a => a.Type.Name).ToArray()));
17551765
}
17561766

17571767
Type callType = typeof(Enumerable);
1758-
if (isQueryable && _methodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, ref args))
1768+
if (isQueryable && _methodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, null, ref args))
17591769
{
17601770
callType = typeof(Queryable);
17611771
}
@@ -1951,7 +1961,7 @@ void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr,
19511961
{
19521962
Expression[] args = { expr };
19531963

1954-
if (!_methodFinder.ContainsMethod(signatures, "F", false, ref args))
1964+
if (!_methodFinder.ContainsMethod(signatures, "F", false, null, ref args))
19551965
{
19561966
throw IncompatibleOperandError(opName, expr, errorPos);
19571967
}
@@ -1984,12 +1994,12 @@ void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref E
19841994
if (nativeOperation != null)
19851995
{
19861996
// first try left operand's equality operators
1987-
found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, ref args);
1997+
found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, null, ref args);
19881998
if (!found)
1989-
found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, ref args);
1999+
found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, null, ref args);
19902000
}
19912001

1992-
if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, ref args))
2002+
if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, null, ref args))
19932003
{
19942004
throw IncompatibleOperandsError(opName, left, right, errorPos);
19952005
}

0 commit comments

Comments
 (0)