Skip to content

Commit 20221fe

Browse files
authored
Merge pull request #647 from stakx/expression-compiler
Add extensibility point for LINQ expression tree compilation
2 parents 9ca1644 + 90dd72a commit 20221fe

12 files changed

+183
-9
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
77

88
## Unreleased
99

10+
#### Added
11+
12+
* `ExpressionCompiler`: An extensibility point for setting up alternate LINQ expression tree compilation strategies (@stakx, #647)
13+
1014
#### Fixed
1115

1216
* More precise `out` parameter detection for mocking COM interfaces with `[in,out]` parameters (@koutinho, #645)

src/Moq/CaptureMatch.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private static Predicate<T> CreatePredicate(Action<T> captureCallback)
7979

8080
private static Predicate<T> CreatePredicate(Action<T> captureCallback, Expression<Func<T, bool>> predicate)
8181
{
82-
var predicateDelegate = predicate.Compile();
82+
var predicateDelegate = predicate.CompileUsingExpressionCompiler();
8383
return value =>
8484
{
8585
var matches = predicateDelegate.Invoke(value);

src/Moq/DefaultExpressionCompiler.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//Copyright (c) 2007. Clarius Consulting, Manas Technology Solutions, InSTEDD
2+
//https://github.com/moq/moq4
3+
//All rights reserved.
4+
//
5+
//Redistribution and use in source and binary forms,
6+
//with or without modification, are permitted provided
7+
//that the following conditions are met:
8+
//
9+
// * Redistributions of source code must retain the
10+
// above copyright notice, this list of conditions and
11+
// the following disclaimer.
12+
//
13+
// * Redistributions in binary form must reproduce
14+
// the above copyright notice, this list of conditions
15+
// and the following disclaimer in the documentation
16+
// and/or other materials provided with the distribution.
17+
//
18+
// * Neither the name of Clarius Consulting, Manas Technology Solutions or InSTEDD nor the
19+
// names of its contributors may be used to endorse
20+
// or promote products derived from this software
21+
// without specific prior written permission.
22+
//
23+
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24+
//CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25+
//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26+
//MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27+
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28+
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29+
//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30+
//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31+
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32+
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
33+
//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34+
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35+
//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36+
//SUCH DAMAGE.
37+
//
38+
//[This is the BSD license, see
39+
// http://www.opensource.org/licenses/bsd-license.php]
40+
41+
using System;
42+
using System.Linq.Expressions;
43+
44+
namespace Moq
45+
{
46+
internal sealed class DefaultExpressionCompiler : ExpressionCompiler
47+
{
48+
new public static readonly DefaultExpressionCompiler Instance = new DefaultExpressionCompiler();
49+
50+
private DefaultExpressionCompiler()
51+
{
52+
}
53+
54+
public override Delegate Compile(LambdaExpression expression)
55+
{
56+
return expression.Compile();
57+
}
58+
59+
public override TDelegate Compile<TDelegate>(Expression<TDelegate> expression)
60+
{
61+
return expression.Compile();
62+
}
63+
}
64+
}

src/Moq/Evaluator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ private static Expression Evaluate(Expression e)
110110
return e;
111111
}
112112
LambdaExpression lambda = Expression.Lambda(e);
113-
Delegate fn = lambda.Compile();
113+
Delegate fn = lambda.CompileUsingExpressionCompiler();
114114
return Expression.Constant(fn.DynamicInvoke(null), e.Type);
115115
}
116116
}

src/Moq/ExpressionCompiler.cs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//Copyright (c) 2007. Clarius Consulting, Manas Technology Solutions, InSTEDD
2+
//https://github.com/moq/moq4
3+
//All rights reserved.
4+
//
5+
//Redistribution and use in source and binary forms,
6+
//with or without modification, are permitted provided
7+
//that the following conditions are met:
8+
//
9+
// * Redistributions of source code must retain the
10+
// above copyright notice, this list of conditions and
11+
// the following disclaimer.
12+
//
13+
// * Redistributions in binary form must reproduce
14+
// the above copyright notice, this list of conditions
15+
// and the following disclaimer in the documentation
16+
// and/or other materials provided with the distribution.
17+
//
18+
// * Neither the name of Clarius Consulting, Manas Technology Solutions or InSTEDD nor the
19+
// names of its contributors may be used to endorse
20+
// or promote products derived from this software
21+
// without specific prior written permission.
22+
//
23+
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24+
//CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25+
//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26+
//MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27+
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28+
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29+
//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30+
//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31+
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32+
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
33+
//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34+
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35+
//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36+
//SUCH DAMAGE.
37+
//
38+
//[This is the BSD license, see
39+
// http://www.opensource.org/licenses/bsd-license.php]
40+
41+
using System;
42+
using System.ComponentModel;
43+
using System.Linq.Expressions;
44+
45+
namespace Moq
46+
{
47+
/// <summary>
48+
/// An <see cref="ExpressionCompiler"/> compiles LINQ expression trees (<see cref="Expression"/>) to delegates.
49+
/// Whenever Moq needs to compile an expression tree, it uses the instance set up by <see cref="ExpressionCompiler.Instance"/>.
50+
/// </summary>
51+
[EditorBrowsable(EditorBrowsableState.Advanced)]
52+
public abstract class ExpressionCompiler
53+
{
54+
private static ExpressionCompiler instance = DefaultExpressionCompiler.Instance;
55+
56+
/// <summary>
57+
/// The default <see cref="ExpressionCompiler"/> instance, which simply delegates to the framework's <see cref="LambdaExpression.Compile"/>.
58+
/// </summary>
59+
public static ExpressionCompiler Default => DefaultExpressionCompiler.Instance;
60+
61+
/// <summary>
62+
/// Gets or sets the <see cref="ExpressionCompiler"/> instance that Moq uses to compile <see cref="Expression"/> (LINQ expression trees).
63+
/// Defaults to <see cref="Default"/>.
64+
/// </summary>
65+
public static ExpressionCompiler Instance
66+
{
67+
get => instance;
68+
set => instance = value ?? throw new ArgumentNullException(nameof(value));
69+
}
70+
71+
/// <summary>
72+
/// Initializes a new instance of the <see cref="ExpressionCompiler"/> class.
73+
/// </summary>
74+
protected ExpressionCompiler()
75+
{
76+
}
77+
78+
/// <summary>
79+
/// Compiles the specified LINQ expression tree.
80+
/// </summary>
81+
/// <param name="expression">The LINQ expression tree that should be compiled.</param>
82+
public abstract Delegate Compile(LambdaExpression expression);
83+
84+
/// <summary>
85+
/// Compiles the specified LINQ expression tree.
86+
/// </summary>
87+
/// <typeparam name="TDelegate">The type of delegate to which the expression will be compiled.</typeparam>
88+
/// <param name="expression">The LINQ expression tree that should be compiled.</param>
89+
public abstract TDelegate Compile<TDelegate>(Expression<TDelegate> expression) where TDelegate : Delegate;
90+
}
91+
}

src/Moq/ExpressionExtensions.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ namespace Moq
5151
{
5252
internal static class ExpressionExtensions
5353
{
54+
internal static Delegate CompileUsingExpressionCompiler(this LambdaExpression expression)
55+
{
56+
// Expression trees are not compiled directly.
57+
// The indirection via an ExpressionCompiler allows users to plug a different expression compiler.
58+
return ExpressionCompiler.Instance.Compile(expression);
59+
}
60+
61+
internal static TDelegate CompileUsingExpressionCompiler<TDelegate>(this Expression<TDelegate> expression) where TDelegate : Delegate
62+
{
63+
// Expression trees are not compiled directly.
64+
// The indirection via an ExpressionCompiler allows users to plug a different expression compiler.
65+
return ExpressionCompiler.Instance.Compile(expression);
66+
}
67+
5468
/// <summary>
5569
/// Converts the body of the lambda expression into the <see cref="PropertyInfo"/> referenced by it.
5670
/// </summary>
@@ -141,7 +155,7 @@ private static bool PartialMatcherAwareEval_ShouldEvaluate(Expression expression
141155
// Evaluate everything but matchers:
142156
using (var context = new FluentMockContext())
143157
{
144-
Expression.Lambda<Action>(expression).Compile().Invoke();
158+
Expression.Lambda<Action>(expression).CompileUsingExpressionCompiler().Invoke();
145159
return context.LastMatch == null;
146160
}
147161

src/Moq/It.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public static TValue IsNotNull<TValue>()
9898
public static TValue Is<TValue>(Expression<Func<TValue, bool>> match)
9999
{
100100
return Match<TValue>.Create(
101-
value => match.Compile().Invoke(value),
101+
value => match.CompileUsingExpressionCompiler().Invoke(value),
102102
() => It.Is<TValue>(match));
103103
}
104104

src/Moq/Linq/MockQuery.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public TResult Execute<TResult>(Expression expression)
109109
var replaced = new MockSetupsBuilder(this.underlyingCreateMocks).Visit(expression);
110110

111111
var lambda = Expression.Lambda<Func<TResult>>(replaced);
112-
return lambda.Compile().Invoke();
112+
return lambda.CompileUsingExpressionCompiler().Invoke();
113113
}
114114

115115
public IEnumerator<T> GetEnumerator()

src/Moq/MatcherFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public static IMatcher CreateMatcher(Expression expression, bool isParams)
8686
// Try to determine if invocation is to a matcher.
8787
using (var context = new FluentMockContext())
8888
{
89-
Expression.Lambda<Action>(call).Compile().Invoke();
89+
Expression.Lambda<Action>(call).CompileUsingExpressionCompiler().Invoke();
9090

9191
if (context.LastMatch != null)
9292
{
@@ -110,7 +110,7 @@ public static IMatcher CreateMatcher(Expression expression, bool isParams)
110110
// Try to determine if invocation is to a matcher.
111111
using (var context = new FluentMockContext())
112112
{
113-
Expression.Lambda<Action>((MemberExpression)expression).Compile().Invoke();
113+
Expression.Lambda<Action>((MemberExpression)expression).CompileUsingExpressionCompiler().Invoke();
114114
if (context.LastMatch != null)
115115
{
116116
return context.LastMatch;

src/Moq/Mock.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,7 @@ private static Mock GetTargetMock(Expression fluentExpression, Mock mock)
852852
var targetExpression = FluentMockVisitor.Accept(fluentExpression, mock);
853853
var targetLambda = Expression.Lambda<Func<Mock>>(Expression.Convert(targetExpression, typeof(Mock)));
854854

855-
var targetObject = targetLambda.Compile()();
855+
var targetObject = targetLambda.CompileUsingExpressionCompiler()();
856856
return targetObject;
857857
}
858858

0 commit comments

Comments
 (0)