Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE]: Use Expression Trees #21

Draft
wants to merge 10 commits into
base: develop
Choose a base branch
from
11 changes: 6 additions & 5 deletions src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
using Hyperbee.Pipeline.Context;
using System.Linq.Expressions;
using Hyperbee.Pipeline.Context;
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline.Binders.Abstractions;

internal abstract class Binder<TInput, TOutput>
{
protected FunctionAsync<TInput, TOutput> Pipeline { get; }
protected Expression<FunctionAsync<TInput, TOutput>> Pipeline { get; }
protected Action<IPipelineContext> Configure { get; }

protected Binder( FunctionAsync<TInput, TOutput> function, Action<IPipelineContext> configure )
protected Binder( Expression<FunctionAsync<TInput, TOutput>> function, Action<IPipelineContext> configure )
{
Pipeline = function;
Configure = configure;
}

protected virtual async Task<(TOutput Result, bool Canceled)> ProcessPipelineAsync( IPipelineContext context, TInput argument )
protected virtual async Task<(TOutput Result, bool Canceled)> ProcessPipelineAsync( IPipelineContext context, TInput argument, FunctionAsync<TInput, TOutput> pipeline )
{
var result = await Pipeline( context, argument ).ConfigureAwait( false );
var result = await pipeline( context, argument ).ConfigureAwait( false );

var contextControl = (IPipelineContextControl) context;
var canceled = contextControl.HandleCancellationRequested( result );
Expand Down
5 changes: 3 additions & 2 deletions src/Hyperbee.Pipeline/Binders/Abstractions/BlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Hyperbee.Pipeline.Context;
using System.Linq.Expressions;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders.Abstractions;

internal abstract class BlockBinder<TInput, TOutput> : Binder<TInput, TOutput>
{
protected BlockBinder( FunctionAsync<TInput, TOutput> function, Action<IPipelineContext> configure )
protected BlockBinder( Expression<FunctionAsync<TInput, TOutput>> function, Action<IPipelineContext> configure )
: base( function, configure )
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders.Abstractions;
Expand All @@ -7,7 +8,7 @@ internal abstract class ConditionalBlockBinder<TInput, TOutput> : BlockBinder<TI
{
protected Function<TOutput, bool> Condition { get; }

protected ConditionalBlockBinder( Function<TOutput, bool> condition, FunctionAsync<TInput, TOutput> function, Action<IPipelineContext> configure )
protected ConditionalBlockBinder( Function<TOutput, bool> condition, Expression<FunctionAsync<TInput, TOutput>> function, Action<IPipelineContext> configure )
: base( function, configure )
{
Condition = condition;
Expand Down
5 changes: 3 additions & 2 deletions src/Hyperbee.Pipeline/Binders/Abstractions/StatementBinder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Hyperbee.Pipeline.Context;
using System.Linq.Expressions;
using Hyperbee.Pipeline.Context;
using Hyperbee.Pipeline.Extensions.Implementation;

namespace Hyperbee.Pipeline.Binders.Abstractions;
Expand All @@ -7,7 +8,7 @@ internal abstract class StatementBinder<TInput, TOutput> : Binder<TInput, TOutpu
{
protected MiddlewareAsync<object, object> Middleware { get; }

protected StatementBinder( FunctionAsync<TInput, TOutput> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
protected StatementBinder( Expression<FunctionAsync<TInput, TOutput>> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
: base( function, configure )
{
Middleware = middleware;
Expand Down
51 changes: 40 additions & 11 deletions src/Hyperbee.Pipeline/Binders/CallBlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,54 @@
using Hyperbee.Pipeline.Binders.Abstractions;
using System.Linq.Expressions;
using System.Reflection;
using Hyperbee.Pipeline.Binders.Abstractions;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders;

internal class CallBlockBinder<TInput, TOutput> : BlockBinder<TInput, TOutput>
{
public CallBlockBinder( FunctionAsync<TInput, TOutput> function )
public CallBlockBinder( Expression<FunctionAsync<TInput, TOutput>> function )
: base( function, default )
{
}

public FunctionAsync<TInput, TOutput> Bind( FunctionAsync<TOutput, object> next )
public Expression<FunctionAsync<TInput, TOutput>> Bind( Expression<FunctionAsync<TOutput, object>> next )
{
return async ( context, argument ) =>
{
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
// Get the MethodInfo for the helper method
var bindImplAsyncMethodInfo = typeof( CallBlockBinder<TInput, TOutput> )
.GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )!;

if ( canceled )
return default;
// Create parameters for the lambda expression
var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" );
var paramArgument = Expression.Parameter( typeof( TInput ), "argument" );

await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false );
return nextArgument;
};
// Create a call expression to the helper method
var callBindImplAsync = Expression.Call(
Expression.Constant( this ),
bindImplAsyncMethodInfo,
next,
Pipeline,
paramContext,
paramArgument
);

// Create and return the final expression
return Expression.Lambda<FunctionAsync<TInput, TOutput>>( callBindImplAsync, paramContext, paramArgument );
}

private async Task<TOutput> BindImplAsync(
FunctionAsync<TOutput, object> next,
FunctionAsync<TInput, TOutput> pipeline,
IPipelineContext context,
TInput argument )
{
var (nextArgument, canceled) =
await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false );

if ( canceled )
return default;

await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false );
return nextArgument;
}
}
52 changes: 41 additions & 11 deletions src/Hyperbee.Pipeline/Binders/CallIfBlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,55 @@
using Hyperbee.Pipeline.Binders.Abstractions;
using System.Linq.Expressions;
using System.Reflection;
using Hyperbee.Pipeline.Binders.Abstractions;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders;

internal class CallIfBlockBinder<TInput, TOutput> : ConditionalBlockBinder<TInput, TOutput>
{
public CallIfBlockBinder( Function<TOutput, bool> condition, FunctionAsync<TInput, TOutput> function )
public CallIfBlockBinder( Function<TOutput, bool> condition, Expression<FunctionAsync<TInput, TOutput>> function )
: base( condition, function, default )
{
}

public FunctionAsync<TInput, TOutput> Bind( FunctionAsync<TOutput, object> next )
public Expression<FunctionAsync<TInput, TOutput>> Bind( Expression<FunctionAsync<TOutput, object>> next )
{
return async ( context, argument ) =>
{
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
// Get the MethodInfo for the helper method
var bindImplAsyncMethodInfo = typeof( CallIfBlockBinder<TInput, TOutput> )
.GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )!;

if ( canceled )
return default;
// Create parameters for the lambda expression
var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" );
var paramArgument = Expression.Parameter( typeof( TInput ), "argument" );

await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false );
return nextArgument;
};
// Create a call expression to the helper method
var callBindImplAsync = Expression.Call(
Expression.Constant( this ),
bindImplAsyncMethodInfo,
next,
Pipeline,
paramContext,
paramArgument
);

// Create and return the final expression
return Expression.Lambda<FunctionAsync<TInput, TOutput>>( callBindImplAsync, paramContext, paramArgument );
}

private async Task<TOutput> BindImplAsync(
FunctionAsync<TOutput, object> next,
FunctionAsync<TInput, TOutput> pipeline,
IPipelineContext context,
TInput argument )
{
var (nextArgument, canceled) =
await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false );

if ( canceled )
return default;

await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false );
return nextArgument;
}

}
87 changes: 70 additions & 17 deletions src/Hyperbee.Pipeline/Binders/CallStatementBinder.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,87 @@
using System.Reflection;
using System.Linq.Expressions;
using System.Reflection;
using Hyperbee.Pipeline.Binders.Abstractions;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders;

internal class CallStatementBinder<TInput, TOutput> : StatementBinder<TInput, TOutput>
{
public CallStatementBinder( FunctionAsync<TInput, TOutput> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
public CallStatementBinder( Expression<FunctionAsync<TInput, TOutput>> function, MiddlewareAsync<object, object> middleware, Action<IPipelineContext> configure )
: base( function, middleware, configure )
{
}

public FunctionAsync<TInput, TOutput> Bind( ProcedureAsync<TOutput> next, MethodInfo method = null )
public Expression<FunctionAsync<TInput, TOutput>> Bind( ProcedureAsync<TOutput> next, MethodInfo method = null )
{
var defaultName = (method ?? next.Method).Name;

return async ( context, argument ) =>
{
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );

if ( canceled )
return default;

return await ProcessStatementAsync(
async ( ctx, arg ) =>
{
await next( ctx, arg ).ConfigureAwait( false );
return arg;
}, context, nextArgument, defaultName ).ConfigureAwait( false );
};
// Get the MethodInfo for the helper method
var bindImplAsyncMethodInfo = typeof( CallStatementBinder<TInput, TOutput> )
.GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )!;

// Create parameters for the lambda expression
var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" );
var paramArgument = Expression.Parameter( typeof( TInput ), "argument" );

// Create a call expression to the helper method
var callBindImplAsync = Expression.Call(
Expression.Constant( this ),
bindImplAsyncMethodInfo,
ExpressionBinder.ToExpression( next ),
Pipeline,
paramContext,
paramArgument,
Expression.Constant( defaultName )
);

// Create and return the final expression
return Expression.Lambda<FunctionAsync<TInput, TOutput>>( callBindImplAsync, paramContext, paramArgument );
}

private async Task<TOutput> BindImplAsync(
ProcedureAsync<TOutput> next,
FunctionAsync<TInput, TOutput> pipeline,
IPipelineContext context,
TInput argument,
string defaultName )
{
var (nextArgument, canceled) =
await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false );

if ( canceled )
return default;

return await ProcessStatementAsync(
async ( ctx, arg ) =>
{
await next( ctx, arg ).ConfigureAwait( false );
return arg;
}, context, nextArgument, defaultName ).ConfigureAwait( false );

}

/*
private async Task<TOutput> Next( ProcedureAsync<TOutput> next, MiddlewareAsync<object, object> middleware, IPipelineContext context, TOutput nextArgument )
{
if ( Middleware == null )
{
await next( context, nextArgument ).ConfigureAwait( false );
return nextArgument;
}

await middleware(
context,
nextArgument,
async ( context1, argument1 ) =>
{
await next( context1, (TOutput) argument1 ).ConfigureAwait( false );
return nextArgument;
}
).ConfigureAwait( false );

return nextArgument;
}*/

}

57 changes: 42 additions & 15 deletions src/Hyperbee.Pipeline/Binders/ForEachBlockBinder.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,60 @@
using Hyperbee.Pipeline.Binders.Abstractions;
using System.Linq.Expressions;
using System.Reflection;
using Hyperbee.Pipeline.Binders.Abstractions;
using Hyperbee.Pipeline.Context;

namespace Hyperbee.Pipeline.Binders;

internal class ForEachBlockBinder<TInput, TOutput, TElement> : BlockBinder<TInput, TOutput>
{
public ForEachBlockBinder( FunctionAsync<TInput, TOutput> function )
public ForEachBlockBinder( Expression<FunctionAsync<TInput, TOutput>> function )
: base( function, default )
{
}

public FunctionAsync<TInput, TOutput> Bind( FunctionAsync<TElement, object> next )
public Expression<FunctionAsync<TInput, TOutput>> Bind( Expression<FunctionAsync<TElement, object>> next )
{
// Get the MethodInfo for the helper method
var bindImplAsyncMethodInfo = typeof( ForEachBlockBinder<TInput, TOutput, TElement> )
.GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )!;

// Create parameters for the lambda expression
var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" );
var paramArgument = Expression.Parameter( typeof( TInput ), "argument" );

// Create a call expression to the helper method
var callBindImplAsync = Expression.Call(
Expression.Constant( this ),
bindImplAsyncMethodInfo,
next,
Pipeline,
paramContext,
paramArgument
);

// Create and return the final expression
return Expression.Lambda<FunctionAsync<TInput, TOutput>>( callBindImplAsync, paramContext, paramArgument );
}

private async Task<TOutput> BindImplAsync(
FunctionAsync<TElement, object> next,
FunctionAsync<TInput, TOutput> pipeline,
IPipelineContext context,
TInput argument )
{
return async ( context, argument ) =>
{
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false );
var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false );

if ( canceled )
return default;
if ( canceled )
return default;

var nextArguments = (IEnumerable<TElement>) nextArgument;
var nextArguments = (IEnumerable<TElement>) nextArgument;

foreach ( var elementArgument in nextArguments )
{
await ProcessBlockAsync( next, context, elementArgument ).ConfigureAwait( false );
}
foreach ( var elementArgument in nextArguments )
{
await ProcessBlockAsync( next, context, elementArgument ).ConfigureAwait( false );
}

return nextArgument;
};
return nextArgument;
}
}

3 changes: 2 additions & 1 deletion src/Hyperbee.Pipeline/Binders/HookBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ internal class HookBinder<TInput, TOutput> // explicit Type Args due to <object,

public HookBinder( MiddlewareAsync<TInput, TOutput> middleware )
{
Middleware = middleware ?? (async ( context, argument, next ) => await next( context, argument ).ConfigureAwait( false ));
Middleware = middleware ?? (async ( context, argument, next ) =>
await next( context, argument ).ConfigureAwait( false ));
}

public MiddlewareAsync<TInput, TOutput> Bind( MiddlewareAsync<TInput, TOutput> middleware )
Expand Down
Loading
Loading