Skip to content

Commit 28ead6f

Browse files
- New Inject<T>() Expression to help with DI
- New ConfigurationValue<T>() Expression to help with getting Config values - New Compile extension to help support DI and Configuration
1 parent d6b12c8 commit 28ead6f

12 files changed

+452
-10
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Linq.Expressions;
2+
3+
namespace Hyperbee.Expressions;
4+
5+
public static partial class ExpressionExtensions
6+
{
7+
public static Func<TResult> Compile<TResult>(
8+
this Expression<Func<TResult>> expression,
9+
IServiceProvider serviceProvider,
10+
bool preferInterpretation = false )
11+
{
12+
ArgumentNullException.ThrowIfNull( serviceProvider, nameof( serviceProvider ) );
13+
14+
var setter = new ServiceSetter( serviceProvider );
15+
16+
return setter.Visit( expression ) is Expression<Func<TResult>> replacedExpression
17+
? replacedExpression.Compile( preferInterpretation )
18+
: throw new InvalidOperationException( "Failed to compile expression." );
19+
}
20+
21+
private class ServiceSetter( IServiceProvider serviceProvider ) : ExpressionVisitor
22+
{
23+
protected override Expression VisitExtension( Expression node )
24+
{
25+
switch ( node )
26+
{
27+
case IDependencyInjectionExpression injectExpression:
28+
injectExpression.SetServiceProvider( serviceProvider );
29+
return node;
30+
default:
31+
return base.VisitExtension( node );
32+
}
33+
}
34+
}
35+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Linq.Expressions;
2+
using System.Reflection;
3+
using Microsoft.Extensions.Configuration;
4+
5+
namespace Hyperbee.Expressions;
6+
7+
public class ConfigurationExpression<T> : Expression, IDependencyInjectionExpression
8+
{
9+
private IConfiguration _configuration;
10+
private readonly string _key;
11+
12+
public ConfigurationExpression( string key )
13+
{
14+
_key = key;
15+
}
16+
17+
public ConfigurationExpression( IConfiguration configuration, string key )
18+
{
19+
_configuration = configuration;
20+
_key = key;
21+
}
22+
23+
public void SetServiceProvider( IServiceProvider serviceProvider )
24+
{
25+
_configuration ??= serviceProvider?.GetService( typeof( IConfiguration ) ) as IConfiguration;
26+
}
27+
28+
public override ExpressionType NodeType => ExpressionType.Extension;
29+
public override Type Type => typeof( T );
30+
public override bool CanReduce => true;
31+
32+
private static readonly MethodInfo GetValueMethodInfo = typeof( ConfigurationBinder )
33+
.GetMethod( "GetValue", [typeof( IConfiguration ), typeof( string )] )!
34+
.MakeGenericMethod( typeof( T ) );
35+
36+
public override Expression Reduce()
37+
{
38+
if ( _configuration == null )
39+
{
40+
throw new InvalidOperationException( "Configuration is not available." );
41+
}
42+
43+
return Call(
44+
null,
45+
GetValueMethodInfo,
46+
[Constant( _configuration ), Constant( _key )] );
47+
}
48+
49+
protected override Expression VisitChildren( ExpressionVisitor visitor )
50+
{
51+
return this;
52+
}
53+
}
54+
public static partial class ExpressionExtensions
55+
{
56+
public static ConfigurationExpression<T> ConfigurationValue<T>( string key )
57+
{
58+
return new ConfigurationExpression<T>( key );
59+
}
60+
61+
public static ConfigurationExpression<T> ConfigurationValue<T>( IConfiguration configuration, string key )
62+
{
63+
return new ConfigurationExpression<T>( configuration, key );
64+
}
65+
}

src/Hyperbee.Expressions/EnumerableBlockExpression.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Collections.ObjectModel;
1+
using System.Collections.ObjectModel;
42
using System.Linq.Expressions;
53
using Hyperbee.Collections;
64
using Hyperbee.Expressions.CompilerServices;

src/Hyperbee.Expressions/Hyperbee.Expressions.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
<None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath="/" />
4646
<PackageReference Include="Hyperbee.Collections" Version="2.4.0" />
4747
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
48+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.3" />
49+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.3" />
50+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
4851
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="8.0.0">
4952
<PrivateAssets>all</PrivateAssets>
5053
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Hyperbee.Expressions;
2+
3+
public interface IDependencyInjectionExpression
4+
{
5+
void SetServiceProvider( IServiceProvider serviceProvider );
6+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System.Linq.Expressions;
2+
using System.Reflection;
3+
using Microsoft.Extensions.DependencyInjection;
4+
5+
namespace Hyperbee.Expressions;
6+
7+
public class InjectExpression<T> : Expression, IDependencyInjectionExpression
8+
{
9+
private IServiceProvider _serviceProvider;
10+
private readonly string _key;
11+
private readonly Expression _defaultValue;
12+
13+
public InjectExpression( IServiceProvider serviceProvider, string key, Expression defaultValue )
14+
{
15+
_serviceProvider = serviceProvider;
16+
_key = key;
17+
_defaultValue = defaultValue;
18+
}
19+
20+
public void SetServiceProvider( IServiceProvider serviceProvider )
21+
{
22+
_serviceProvider ??= serviceProvider;
23+
}
24+
25+
public override ExpressionType NodeType => ExpressionType.Extension;
26+
public override Type Type => typeof( T );
27+
public override bool CanReduce => true;
28+
29+
private static readonly MethodInfo GetServiceMethodInfo = typeof( ServiceProviderServiceExtensions )
30+
.GetMethod( "GetService", [typeof( IServiceProvider )] )!
31+
.MakeGenericMethod( typeof( T ) );
32+
33+
private static readonly MethodInfo GetKeyedServiceMethodInfo = typeof( ServiceProviderKeyedServiceExtensions )
34+
.GetMethod( "GetKeyedService", [typeof( IServiceProvider ), typeof( string )] )!
35+
.MakeGenericMethod( typeof( T ) );
36+
37+
public override Expression Reduce()
38+
{
39+
if ( _serviceProvider == null )
40+
{
41+
throw new InvalidOperationException( "Service provider is not available." );
42+
}
43+
44+
var serviceValue = Variable( typeof( T ), "serviceValue" );
45+
46+
var callExpression = _key == null
47+
? Call(
48+
null,
49+
GetServiceMethodInfo,
50+
Constant( _serviceProvider )
51+
)
52+
: Call(
53+
null,
54+
GetKeyedServiceMethodInfo,
55+
[Constant( _serviceProvider ), Constant( _key )]
56+
);
57+
58+
var defaultExpression = _defaultValue ??
59+
Throw( New( ExceptionHelper.ConstructorInfo, Constant( "Service is not available." ) ), typeof( T ) );
60+
61+
return Block(
62+
[serviceValue],
63+
Assign( serviceValue, callExpression ),
64+
Condition(
65+
NotEqual( serviceValue, Constant( null, typeof( T ) ) ),
66+
serviceValue,
67+
defaultExpression
68+
)
69+
);
70+
}
71+
72+
protected override Expression VisitChildren( ExpressionVisitor visitor )
73+
{
74+
return this;
75+
}
76+
}
77+
78+
internal static class ExceptionHelper
79+
{
80+
internal static readonly ConstructorInfo ConstructorInfo = typeof( InvalidOperationException )
81+
.GetConstructor( [typeof( string )] );
82+
}
83+
84+
public static partial class ExpressionExtensions
85+
{
86+
public static InjectExpression<T> Inject<T>( IServiceProvider serviceProvider, string key = null, Expression defaultValue = null )
87+
{
88+
return new InjectExpression<T>( serviceProvider, key, defaultValue );
89+
}
90+
91+
public static InjectExpression<T> Inject<T>( string key = null, Expression defaultValue = null )
92+
{
93+
return new InjectExpression<T>( null, key, defaultValue );
94+
}
95+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using Hyperbee.Expressions.Tests.TestSupport;
2+
using Microsoft.Extensions.Configuration;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Hosting;
5+
using static System.Linq.Expressions.Expression;
6+
using static Hyperbee.Expressions.ExpressionExtensions;
7+
8+
namespace Hyperbee.Expressions.Tests;
9+
10+
[TestClass]
11+
public class ConfigurationExpressionTests
12+
{
13+
[DataTestMethod]
14+
[DataRow( CompilerType.Fast )]
15+
[DataRow( CompilerType.System )]
16+
[DataRow( CompilerType.Interpret )]
17+
public void ConfigurationExpression_ShouldConfigurationSuccessfully_WithConfiguration( CompilerType compiler )
18+
{
19+
// Arrange
20+
var configuration = GetServiceProvider().GetService<IConfiguration>();
21+
22+
var block = ConfigurationValue<string>( configuration, Key );
23+
24+
// Act
25+
var lambda = Lambda<Func<string>>( block );
26+
var compiledLambda = lambda.Compile( compiler );
27+
28+
var result = compiledLambda();
29+
30+
// Assert
31+
Assert.AreEqual( Value, result );
32+
}
33+
34+
[DataTestMethod]
35+
[DataRow( false )]
36+
[DataRow( true )]
37+
public void ConfigurationExpression_ShouldConfigurationSuccessfully_WithCustomCompile( bool interpret )
38+
{
39+
// Arrange
40+
var block = ConfigurationValue<string>( Key );
41+
42+
// Act
43+
var lambda = Lambda<Func<string>>( block );
44+
var compiledLambda = lambda.Compile( GetServiceProvider(), interpret );
45+
46+
var result = compiledLambda();
47+
48+
// Assert
49+
Assert.AreEqual( Value, result );
50+
}
51+
52+
private const string Key = "Hello";
53+
private const string Value = "Hello, World!";
54+
55+
private static IServiceProvider GetServiceProvider()
56+
{
57+
var host = Host.CreateDefaultBuilder()
58+
.ConfigureAppConfiguration( ( _, config ) =>
59+
{
60+
config.AddInMemoryCollection( new Dictionary<string, string>
61+
{
62+
{Key, Value}
63+
} );
64+
} )
65+
.Build();
66+
67+
return host.Services;
68+
}
69+
70+
}

test/Hyperbee.Expressions.Tests/ForEachTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,9 @@ public async Task ForEachExpression_ShouldIterateOverCollection_WithAwaits( Comp
140140
}
141141

142142
[DataTestMethod]
143-
//[DataRow( CompilerType.Fast )]
143+
[DataRow( CompilerType.Fast )]
144144
[DataRow( CompilerType.System )]
145-
//[DataRow( CompilerType.Interpret )]
145+
[DataRow( CompilerType.Interpret )]
146146
public void ForEachExpression_ShouldIterateOverCollection_WithYields( CompilerType compiler )
147147
{
148148
// Arrange

test/Hyperbee.Expressions.Tests/ForExpressionTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public void ForExpression_ShouldSupportCustomBreak( CompilerType compiler )
5858
var condition = LessThan( counter, Constant( 10 ) );
5959
var iteration = PostIncrementAssign( counter );
6060

61-
var forExpr = For( counterInit, condition, iteration, ( breakLabel, continueLabel ) =>
61+
var forExpr = For( counterInit, condition, iteration, ( breakLabel, _ ) =>
6262
IfThenElse(
6363
Equal( counter, Constant( 5 ) ),
6464
Break( breakLabel ), // break when counter == 5
@@ -82,9 +82,9 @@ public void ForExpression_ShouldSupportCustomBreak( CompilerType compiler )
8282
}
8383

8484
[DataTestMethod]
85-
//[DataRow( CompilerType.Fast )]
85+
[DataRow( CompilerType.Fast )]
8686
[DataRow( CompilerType.System )]
87-
//[DataRow( CompilerType.Interpret )]
87+
[DataRow( CompilerType.Interpret )]
8888
public void ForExpression_ShouldIterateOverCollection_WithYields( CompilerType compiler )
8989
{
9090
// Arrange

test/Hyperbee.Expressions.Tests/Hyperbee.Expressions.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1919
</PackageReference>
2020
<PackageReference Include="FastExpressionCompiler" Version="5.1.0" />
21+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.3" />
22+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
23+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
2124
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
2225
<PackageReference Include="MSTest.TestAdapter" Version="3.8.3" />
2326
<PackageReference Include="MSTest.TestFramework" Version="3.8.3" />

0 commit comments

Comments
 (0)