diff --git a/src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs b/src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs index eb97279..a70f4e4 100644 --- a/src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs +++ b/src/Hyperbee.Pipeline/Binders/Abstractions/Binder.cs @@ -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 { - protected FunctionAsync Pipeline { get; } + protected Expression> Pipeline { get; } protected Action Configure { get; } - protected Binder( FunctionAsync function, Action configure ) + protected Binder( Expression> function, Action 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 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 ); diff --git a/src/Hyperbee.Pipeline/Binders/Abstractions/BlockBinder.cs b/src/Hyperbee.Pipeline/Binders/Abstractions/BlockBinder.cs index 834d976..31c8ec2 100644 --- a/src/Hyperbee.Pipeline/Binders/Abstractions/BlockBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/Abstractions/BlockBinder.cs @@ -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 : Binder { - protected BlockBinder( FunctionAsync function, Action configure ) + protected BlockBinder( Expression> function, Action configure ) : base( function, configure ) { } diff --git a/src/Hyperbee.Pipeline/Binders/Abstractions/ConditionalBlockBinder.cs b/src/Hyperbee.Pipeline/Binders/Abstractions/ConditionalBlockBinder.cs index 6f23e81..a579586 100644 --- a/src/Hyperbee.Pipeline/Binders/Abstractions/ConditionalBlockBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/Abstractions/ConditionalBlockBinder.cs @@ -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; @@ -7,7 +8,7 @@ internal abstract class ConditionalBlockBinder : BlockBinder Condition { get; } - protected ConditionalBlockBinder( Function condition, FunctionAsync function, Action configure ) + protected ConditionalBlockBinder( Function condition, Expression> function, Action configure ) : base( function, configure ) { Condition = condition; diff --git a/src/Hyperbee.Pipeline/Binders/Abstractions/StatementBinder.cs b/src/Hyperbee.Pipeline/Binders/Abstractions/StatementBinder.cs index b5fb44c..f468230 100644 --- a/src/Hyperbee.Pipeline/Binders/Abstractions/StatementBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/Abstractions/StatementBinder.cs @@ -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; @@ -7,7 +8,7 @@ internal abstract class StatementBinder : Binder Middleware { get; } - protected StatementBinder( FunctionAsync function, MiddlewareAsync middleware, Action configure ) + protected StatementBinder( Expression> function, MiddlewareAsync middleware, Action configure ) : base( function, configure ) { Middleware = middleware; diff --git a/src/Hyperbee.Pipeline/Binders/CallBlockBinder.cs b/src/Hyperbee.Pipeline/Binders/CallBlockBinder.cs index 75ebd4b..20e591e 100644 --- a/src/Hyperbee.Pipeline/Binders/CallBlockBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/CallBlockBinder.cs @@ -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 : BlockBinder { - public CallBlockBinder( FunctionAsync function ) + public CallBlockBinder( Expression> function ) : base( function, default ) { } - public FunctionAsync Bind( FunctionAsync next ) + public Expression> Bind( Expression> 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 ) + .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>( callBindImplAsync, paramContext, paramArgument ); + } + + private async Task BindImplAsync( + FunctionAsync next, + FunctionAsync 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; } } diff --git a/src/Hyperbee.Pipeline/Binders/CallIfBlockBinder.cs b/src/Hyperbee.Pipeline/Binders/CallIfBlockBinder.cs index 55494c8..e72b802 100644 --- a/src/Hyperbee.Pipeline/Binders/CallIfBlockBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/CallIfBlockBinder.cs @@ -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 : ConditionalBlockBinder { - public CallIfBlockBinder( Function condition, FunctionAsync function ) + public CallIfBlockBinder( Function condition, Expression> function ) : base( condition, function, default ) { } - public FunctionAsync Bind( FunctionAsync next ) + public Expression> Bind( Expression> 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 ) + .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>( callBindImplAsync, paramContext, paramArgument ); } + + private async Task BindImplAsync( + FunctionAsync next, + FunctionAsync 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; + } + } diff --git a/src/Hyperbee.Pipeline/Binders/CallStatementBinder.cs b/src/Hyperbee.Pipeline/Binders/CallStatementBinder.cs index ab4f296..5f50770 100644 --- a/src/Hyperbee.Pipeline/Binders/CallStatementBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/CallStatementBinder.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Linq.Expressions; +using System.Reflection; using Hyperbee.Pipeline.Binders.Abstractions; using Hyperbee.Pipeline.Context; @@ -6,29 +7,81 @@ namespace Hyperbee.Pipeline.Binders; internal class CallStatementBinder : StatementBinder { - public CallStatementBinder( FunctionAsync function, MiddlewareAsync middleware, Action configure ) + public CallStatementBinder( Expression> function, MiddlewareAsync middleware, Action configure ) : base( function, middleware, configure ) { } - public FunctionAsync Bind( ProcedureAsync next, MethodInfo method = null ) + public Expression> Bind( ProcedureAsync 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 ) + .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>( callBindImplAsync, paramContext, paramArgument ); + } + + private async Task BindImplAsync( + ProcedureAsync next, + FunctionAsync 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 Next( ProcedureAsync next, MiddlewareAsync 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; + }*/ + } diff --git a/src/Hyperbee.Pipeline/Binders/ForEachBlockBinder.cs b/src/Hyperbee.Pipeline/Binders/ForEachBlockBinder.cs index 7c2b00a..14e9d32 100644 --- a/src/Hyperbee.Pipeline/Binders/ForEachBlockBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/ForEachBlockBinder.cs @@ -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 : BlockBinder { - public ForEachBlockBinder( FunctionAsync function ) + public ForEachBlockBinder( Expression> function ) : base( function, default ) { } - public FunctionAsync Bind( FunctionAsync next ) + public Expression> Bind( Expression> next ) + { + // Get the MethodInfo for the helper method + var bindImplAsyncMethodInfo = typeof( ForEachBlockBinder ) + .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>( callBindImplAsync, paramContext, paramArgument ); + } + private async Task BindImplAsync( + FunctionAsync next, + FunctionAsync 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) nextArgument; + var nextArguments = (IEnumerable) 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; } } diff --git a/src/Hyperbee.Pipeline/Binders/HookBinder.cs b/src/Hyperbee.Pipeline/Binders/HookBinder.cs index 5ece4da..8ca720d 100644 --- a/src/Hyperbee.Pipeline/Binders/HookBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/HookBinder.cs @@ -6,7 +6,8 @@ internal class HookBinder // explicit Type Args due to 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 Bind( MiddlewareAsync middleware ) diff --git a/src/Hyperbee.Pipeline/Binders/PipeBlockBinder.cs b/src/Hyperbee.Pipeline/Binders/PipeBlockBinder.cs index 7b081a7..a2fbdd7 100644 --- a/src/Hyperbee.Pipeline/Binders/PipeBlockBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/PipeBlockBinder.cs @@ -1,24 +1,53 @@ -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 PipeBlockBinder : BlockBinder { - public PipeBlockBinder( FunctionAsync function ) + public PipeBlockBinder( Expression> function ) : base( function, default ) { } - public FunctionAsync Bind( FunctionAsync next ) + public Expression> Bind( Expression> next ) { - return async ( context, argument ) => - { - var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false ); + // Get the MethodInfo for the helper method + var bindImplAsyncMethodInfo = typeof( PipeBlockBinder ) + .GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )! + .MakeGenericMethod( typeof( TNext ) ); - if ( canceled ) - return default; + // Create parameters for the lambda expression + var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" ); + var paramArgument = Expression.Parameter( typeof( TInput ), "argument" ); - return await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false ); - }; + // 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>( callBindImplAsync, paramContext, paramArgument ); + } + + private async Task BindImplAsync( + FunctionAsync next, + FunctionAsync pipeline, + IPipelineContext context, + TInput argument ) + { + var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false ); + + if ( canceled ) + return default; + + return await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false ); } } diff --git a/src/Hyperbee.Pipeline/Binders/PipeIfBlockBinder.cs b/src/Hyperbee.Pipeline/Binders/PipeIfBlockBinder.cs index 16219d1..15ddf5f 100644 --- a/src/Hyperbee.Pipeline/Binders/PipeIfBlockBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/PipeIfBlockBinder.cs @@ -1,24 +1,53 @@ -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 PipeIfBlockBinder : ConditionalBlockBinder { - public PipeIfBlockBinder( Function condition, FunctionAsync function ) + public PipeIfBlockBinder( Function condition, Expression> function ) : base( condition, function, default ) { } - public FunctionAsync Bind( FunctionAsync next ) + public Expression> Bind( Expression> next ) { - return async ( context, argument ) => - { - var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false ); + // Get the MethodInfo for the helper method + var bindImplAsyncMethodInfo = typeof( PipeIfBlockBinder ) + .GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )! + .MakeGenericMethod( typeof( TNext ) ); - if ( canceled ) - return default; + // Create parameters for the lambda expression + var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" ); + var paramArgument = Expression.Parameter( typeof( TInput ), "argument" ); - return await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false ); - }; + // 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>( callBindImplAsync, paramContext, paramArgument ); + } + + private async Task BindImplAsync( + FunctionAsync next, + FunctionAsync pipeline, + IPipelineContext context, + TInput argument ) + { + var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false ); + + if ( canceled ) + return default; + + return await ProcessBlockAsync( next, context, nextArgument ).ConfigureAwait( false ); } } diff --git a/src/Hyperbee.Pipeline/Binders/PipeStatementBinder.cs b/src/Hyperbee.Pipeline/Binders/PipeStatementBinder.cs index cfdb841..37fe019 100644 --- a/src/Hyperbee.Pipeline/Binders/PipeStatementBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/PipeStatementBinder.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Linq.Expressions; +using System.Reflection; using Hyperbee.Pipeline.Binders.Abstractions; using Hyperbee.Pipeline.Context; @@ -6,23 +7,54 @@ namespace Hyperbee.Pipeline.Binders; internal class PipeStatementBinder : StatementBinder { - public PipeStatementBinder( FunctionAsync function, MiddlewareAsync middleware, Action configure ) + public PipeStatementBinder( Expression> function, + MiddlewareAsync middleware, Action configure ) : base( function, middleware, configure ) { } - public FunctionAsync Bind( FunctionAsync next, MethodInfo method = null ) + public Expression> Bind( FunctionAsync next, + MethodInfo method = null ) { var defaultName = (method ?? next.Method).Name; - return async ( context, argument ) => - { - var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false ); + // Get the MethodInfo for the helper method + var bindImplAsyncMethodInfo = typeof( PipeStatementBinder ) + .GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )! + .MakeGenericMethod( typeof( TNext ) ); - if ( canceled ) - return default; + // Create parameters for the lambda expression + var paramContext = Expression.Parameter( typeof( IPipelineContext ), "context" ); + var paramArgument = Expression.Parameter( typeof( TInput ), "argument" ); - return await ProcessStatementAsync( next, context, nextArgument, defaultName ).ConfigureAwait( false ); - }; + // 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>( callBindImplAsync, paramContext, paramArgument ); + } + + private async Task BindImplAsync( + FunctionAsync next, + FunctionAsync pipeline, + IPipelineContext context, + TInput argument, + string defaultName ) + { + var (nextArgument, canceled) = + await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false ); + + if ( canceled ) + return default; + + return await ProcessStatementAsync( next, context, nextArgument, defaultName ).ConfigureAwait( false ); } } diff --git a/src/Hyperbee.Pipeline/Binders/ReduceBlockBinder.cs b/src/Hyperbee.Pipeline/Binders/ReduceBlockBinder.cs index a184fb0..d0bd874 100644 --- a/src/Hyperbee.Pipeline/Binders/ReduceBlockBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/ReduceBlockBinder.cs @@ -1,4 +1,7 @@ -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; @@ -6,33 +9,58 @@ internal class ReduceBlockBinder : BlockBinder { private Func Reducer { get; } - public ReduceBlockBinder( Func reducer, FunctionAsync function ) - : base( function, default ) + public ReduceBlockBinder( Func reducer, Expression> function ) + : base( function, default ) { Reducer = reducer; } - public FunctionAsync Bind( FunctionAsync next ) + public Expression> Bind( Expression> next ) { - return async ( context, argument ) => - { - var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false ); + // Get the MethodInfo for the BindImpl method + var bindImplAsyncMethodInfo = typeof( ReduceBlockBinder ) + .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>( callBindImplAsync, paramContext, paramArgument ); + } + + private async Task BindImplAsync( + FunctionAsync next, + FunctionAsync pipeline, + IPipelineContext context, + TInput argument ) + { + var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false ); - if ( canceled ) - return default; + if ( canceled ) + return default; - var nextArguments = (IEnumerable) nextArgument; - var accumulator = default( TNext ); + var nextArguments = (IEnumerable) nextArgument; + var accumulator = default( TNext ); - // Process each element and apply the reducer - foreach ( var elementArgument in nextArguments ) - { - var result = await ProcessBlockAsync( next, context, elementArgument ).ConfigureAwait( false ); - accumulator = Reducer( accumulator, result ); - } + // Process each element and apply the reducer + foreach ( var elementArgument in nextArguments ) + { + var result = await ProcessBlockAsync( next, context, elementArgument ).ConfigureAwait( false ); + accumulator = Reducer( accumulator, result ); + } - return accumulator; - }; + return accumulator; } } diff --git a/src/Hyperbee.Pipeline/Binders/WaitAllBlockBinder.cs b/src/Hyperbee.Pipeline/Binders/WaitAllBlockBinder.cs index e93eea9..3ecac3d 100644 --- a/src/Hyperbee.Pipeline/Binders/WaitAllBlockBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/WaitAllBlockBinder.cs @@ -1,44 +1,79 @@ -using Hyperbee.Pipeline.Binders.Abstractions; +using System.Linq.Expressions; +using System.Reflection; +using Hyperbee.Pipeline.Binders.Abstractions; using Hyperbee.Pipeline.Context; using Hyperbee.Pipeline.Extensions; using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline.Binders; - internal class WaitAllBlockBinder : ConditionalBlockBinder { private MiddlewareAsync Middleware { get; } - public WaitAllBlockBinder( FunctionAsync function, MiddlewareAsync middleware, Action configure ) - : this( null, function, middleware, configure ) + public WaitAllBlockBinder( + Expression> function, + MiddlewareAsync middleware, + Action configure ) + : base( null, function, configure ) { + Middleware = middleware; } - public WaitAllBlockBinder( Function condition, FunctionAsync function, MiddlewareAsync middleware, Action configure ) + public WaitAllBlockBinder( + Function condition, + Expression> function, + MiddlewareAsync middleware, + Action configure ) : base( condition, function, configure ) { Middleware = middleware; } - public FunctionAsync Bind( FunctionAsync[] nexts, WaitAllReducer reducer ) + public Expression> Bind( FunctionAsync[] nexts, WaitAllReducer reducer ) { - ArgumentNullException.ThrowIfNull( reducer ); + // Get the MethodInfo for the helper method + var bindImplAsyncMethodInfo = typeof( WaitAllBlockBinder ) + .GetMethod( nameof( BindImplAsync ), BindingFlags.NonPublic | BindingFlags.Instance )! + .MakeGenericMethod( typeof( TNext ) ); + + // 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, + Expression.Constant( nexts ), + Expression.Constant( reducer ), + Pipeline, + paramContext, + paramArgument + ); + + // Create and return the final expression + return Expression.Lambda>( callBindImplAsync, paramContext, paramArgument ); + } - return async ( context, argument ) => - { - var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument ).ConfigureAwait( false ); + private async Task BindImplAsync( + FunctionAsync[] nexts, + WaitAllReducer reducer, + FunctionAsync pipeline, + IPipelineContext context, + TInput argument ) + { + var (nextArgument, canceled) = await ProcessPipelineAsync( context, argument, pipeline ).ConfigureAwait( false ); - if ( canceled ) - return default; + if ( canceled ) + return default; - // WaitAllBlockBinder is unique in that it is both a block configure and a step. - // The reducer is the step action, and because it is a step, we need to ensure - // that middleware is called. Middleware requires us to pass in the execution - // function that it wraps. This requires an additional level of wrapping. + // WaitAllBlockBinder is unique in that it is both a block configure and a step. + // The reducer is the step action, and because it is a step, we need to ensure + // that middleware is called. Middleware requires us to pass in the execution + // function that it wraps. This requires an additional level of wrapping. - return await WaitAllAsync( context, nextArgument, nexts, reducer ).ConfigureAwait( false ); - }; + return await WaitAllAsync( context, nextArgument, nexts, reducer ).ConfigureAwait( false ); } private async Task WaitAllAsync( IPipelineContext context, TOutput nextArgument, FunctionAsync[] nexts, WaitAllReducer reducer ) diff --git a/src/Hyperbee.Pipeline/Binders/WrapBinder.cs b/src/Hyperbee.Pipeline/Binders/WrapBinder.cs index 471738f..cdab228 100644 --- a/src/Hyperbee.Pipeline/Binders/WrapBinder.cs +++ b/src/Hyperbee.Pipeline/Binders/WrapBinder.cs @@ -1,4 +1,6 @@ -using Hyperbee.Pipeline.Context; +using System.Linq.Expressions; +using System.Reflection; +using Hyperbee.Pipeline.Context; using Hyperbee.Pipeline.Extensions.Implementation; namespace Hyperbee.Pipeline.Binders; @@ -14,21 +16,46 @@ public WrapBinder( MiddlewareAsync middleware, Action Bind( FunctionAsync next ) + public Expression> Bind( Expression> next ) + { + // Get the MethodInfo for the helper method + var bindImplAsyncMethodInfo = typeof( WrapBinder ) + .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, + ExpressionBinder.ToExpression( Middleware ), + paramContext, + paramArgument + ); + + // Create and return the final expression + return Expression.Lambda>( callBindImplAsync, paramContext, paramArgument ); + } + + private async Task BindImplAsync( + FunctionAsync next, + MiddlewareAsync middleware, + IPipelineContext context, + TInput argument ) { var defaultName = next.Method.Name; - return async ( context, argument ) => - { - var contextControl = (IPipelineContextControl) context; + var contextControl = (IPipelineContextControl) context; - using var _ = contextControl.CreateFrame( context, Configure, defaultName ); + using var _ = contextControl.CreateFrame( context, Configure, defaultName ); - return await Middleware( - context, - argument, - async ( context1, argument1 ) => await next( context1, argument1 ).ConfigureAwait( false ) - ).ConfigureAwait( false ); - }; + return await middleware( + context, + argument, + async ( context1, argument1 ) => await next( context1, argument1 ).ConfigureAwait( false ) + ).ConfigureAwait( false ); } } diff --git a/src/Hyperbee.Pipeline/Builders/CallBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/CallBlockBuilder.cs index 3d57e25..5d19ebd 100644 --- a/src/Hyperbee.Pipeline/Builders/CallBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/CallBlockBuilder.cs @@ -40,7 +40,7 @@ Func, IPipelineBuilder> builder return new PipelineBuilder { - Function = new CallBlockBinder( parentFunction ).Bind( function ), + Function = new CallBlockBinder( parentFunction ).Bind( ExpressionBinder.ToExpression( function ) ), Middleware = parentMiddleware }; } diff --git a/src/Hyperbee.Pipeline/Builders/CallIfBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/CallIfBlockBuilder.cs index 90b7993..fc33eea 100644 --- a/src/Hyperbee.Pipeline/Builders/CallIfBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/CallIfBlockBuilder.cs @@ -43,7 +43,7 @@ Func, IPipelineBuilder> builder return new PipelineBuilder { - Function = new CallIfBlockBinder( condition, parentFunction ).Bind( function ), + Function = new CallIfBlockBinder( condition, parentFunction ).Bind( ExpressionBinder.ToExpression( function ) ), Middleware = parentMiddleware }; } diff --git a/src/Hyperbee.Pipeline/Builders/ExpressionBinder.cs b/src/Hyperbee.Pipeline/Builders/ExpressionBinder.cs new file mode 100644 index 0000000..6b5b8dc --- /dev/null +++ b/src/Hyperbee.Pipeline/Builders/ExpressionBinder.cs @@ -0,0 +1,15 @@ +using System.Linq.Expressions; + +namespace Hyperbee.Pipeline; + +public static class ExpressionBinder +{ + public static Expression> ToExpression( FunctionAsync function ) => + ( context, input ) => function( context, input ); + + public static Expression> ToExpression( ProcedureAsync function ) => + ( context, argument ) => function( context, argument ); + + public static Expression> ToExpression( MiddlewareAsync middleware ) => + ( context, argument, next ) => middleware( context, argument, next ); +} diff --git a/src/Hyperbee.Pipeline/Builders/ForEachBlockBuilder.cs b/src/Hyperbee.Pipeline/Builders/ForEachBlockBuilder.cs index 2456159..0cd96bd 100644 --- a/src/Hyperbee.Pipeline/Builders/ForEachBlockBuilder.cs +++ b/src/Hyperbee.Pipeline/Builders/ForEachBlockBuilder.cs @@ -41,7 +41,7 @@ Func, IPipelineBuilder> builder return new PipelineBuilder { - Function = new ForEachBlockBinder( parentFunction ).Bind( function ), + Function = new ForEachBlockBinder( parentFunction ).Bind( ExpressionBinder.ToExpression( function ) ), Middleware = parentMiddleware }; } diff --git a/src/Hyperbee.Pipeline/IPipelineFunction.cs b/src/Hyperbee.Pipeline/IPipelineFunction.cs index e4b6b04..fd33b96 100644 --- a/src/Hyperbee.Pipeline/IPipelineFunction.cs +++ b/src/Hyperbee.Pipeline/IPipelineFunction.cs @@ -1,4 +1,6 @@ -namespace Hyperbee.Pipeline; +using System.Linq.Expressions; + +namespace Hyperbee.Pipeline; public interface IPipelineFunctionProvider { @@ -9,10 +11,10 @@ public interface IPipelineFunctionProvider public interface IPipelineFunction { - FunctionAsync Function { get; } + Expression> Function { get; } MiddlewareAsync Middleware { get; } - void Deconstruct( out FunctionAsync function, out MiddlewareAsync middleware ) + void Deconstruct( out Expression> function, out MiddlewareAsync middleware ) { function = Function; middleware = Middleware; diff --git a/src/Hyperbee.Pipeline/PipelineBuilder.cs b/src/Hyperbee.Pipeline/PipelineBuilder.cs index 2dfe12d..9669c38 100644 --- a/src/Hyperbee.Pipeline/PipelineBuilder.cs +++ b/src/Hyperbee.Pipeline/PipelineBuilder.cs @@ -1,10 +1,11 @@ -using Hyperbee.Pipeline.Data; +using System.Linq.Expressions; +using Hyperbee.Pipeline.Data; namespace Hyperbee.Pipeline; public class PipelineBuilder : PipelineFactory, IPipelineStartBuilder, IPipelineFunctionProvider { - internal FunctionAsync Function { get; init; } + internal Expression> Function { get; init; } internal MiddlewareAsync Middleware { get; init; } internal PipelineBuilder() @@ -14,11 +15,13 @@ internal PipelineBuilder() public FunctionAsync Build() { // build and return the outermost method + var compiledPipeline = Function.Compile(); + return async ( context, argument ) => { try { - var result = await Function( context, argument ).ConfigureAwait( false ); + var result = await compiledPipeline( context, argument ).ConfigureAwait( false ); if ( context.CancellationToken.IsCancellationRequested ) return Converter.TryConvertTo( context.CancellationValue, out var converted ) ? converted : default; @@ -39,12 +42,14 @@ public FunctionAsync Build() public ProcedureAsync BuildAsProcedure() { + var compiledPipeline = Function.Compile(); + // build and return the outermost method return async ( context, argument ) => { try { - await Function( context, argument ).ConfigureAwait( false ); + await compiledPipeline( context, argument ).ConfigureAwait( false ); } catch ( Exception ex ) { @@ -58,9 +63,11 @@ public ProcedureAsync BuildAsProcedure() FunctionAsync IPipelineBuilder.CastFunction() { + var compiledPipeline = Function.Compile(); + return async ( context, argument ) => { - var result = await Function( context, Cast( argument ) ).ConfigureAwait( false ); + var result = await compiledPipeline( context, Cast( argument ) ).ConfigureAwait( false ); return Cast( result ); }; @@ -79,7 +86,7 @@ IPipelineFunction IPipelineFunctionProvider.Ge public record PipelineFunction : IPipelineFunction { - public FunctionAsync Function { get; init; } + public Expression> Function { get; init; } public MiddlewareAsync Middleware { get; init; } } } diff --git a/src/Hyperbee.Pipeline/PipelineFactory.cs b/src/Hyperbee.Pipeline/PipelineFactory.cs index 578dfd1..ce9d2df 100644 --- a/src/Hyperbee.Pipeline/PipelineFactory.cs +++ b/src/Hyperbee.Pipeline/PipelineFactory.cs @@ -1,4 +1,6 @@ -namespace Hyperbee.Pipeline; +using System.Linq.Expressions; + +namespace Hyperbee.Pipeline; // A quick note about generic arguments. Remember that the builder methods are // forward-looking and are always building the 'next' step. @@ -32,6 +34,7 @@ public static IPipelineStartBuilder Start() internal static IPipelineStartBuilder Start( MiddlewareAsync functionMiddleware ) { + // IPipelineContext context, TInput argument, FunctionAsync next return new PipelineBuilder { Function = ( context, argument ) => Task.FromResult( argument ), diff --git a/src/Hyperbee.Pipline.Caching/PipelineDistributedCacheExtensions.cs b/src/Hyperbee.Pipline.Caching/PipelineDistributedCacheExtensions.cs index 6fd61ee..bec22a7 100644 --- a/src/Hyperbee.Pipline.Caching/PipelineDistributedCacheExtensions.cs +++ b/src/Hyperbee.Pipline.Caching/PipelineDistributedCacheExtensions.cs @@ -1,5 +1,4 @@ -using Hyperbee.Pipeline.Extensions.Implementation; -using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -16,9 +15,9 @@ public static IPipelineBuilder PipeDistributedCacheAsync(); - var function = nestedBuilder( block ).GetPipelineFunction(); + var function = nestedBuilder( block ).CastFunction(); - return builder.PipeDistributedCacheAsync( function.Function, optionsFunc ); + return builder.PipeDistributedCacheAsync( function, optionsFunc ); } public static IPipelineBuilder PipeDistributedCacheAsync( diff --git a/src/Hyperbee.Pipline.Caching/PipelineMemoryCacheExtensions.cs b/src/Hyperbee.Pipline.Caching/PipelineMemoryCacheExtensions.cs index ab19f2e..ac3f325 100644 --- a/src/Hyperbee.Pipline.Caching/PipelineMemoryCacheExtensions.cs +++ b/src/Hyperbee.Pipline.Caching/PipelineMemoryCacheExtensions.cs @@ -1,5 +1,4 @@ -using Hyperbee.Pipeline.Extensions.Implementation; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -16,9 +15,9 @@ public static IPipelineBuilder PipeCache( ArgumentNullException.ThrowIfNull( nestedBuilder ); var block = PipelineFactory.Start(); - var function = nestedBuilder( block ).GetPipelineFunction(); + var function = nestedBuilder( block ).CastFunction(); - return builder.PipeCacheAsync( function.Function, optionsFunc ); + return builder.PipeCacheAsync( function, optionsFunc ); } public static IPipelineBuilder PipeCacheAsync( @@ -29,9 +28,9 @@ public static IPipelineBuilder PipeCacheAsync(); - var function = nestedBuilder( block ).GetPipelineFunction(); + var function = nestedBuilder( block ).CastFunction(); - return builder.PipeCacheAsync( function.Function, optionsFunc ); + return builder.PipeCacheAsync( function, optionsFunc ); } public static IPipelineBuilder PipeCache( diff --git a/test/Hyperbee.Pipleline.Benchmark/PipelineBenchmarks.cs b/test/Hyperbee.Pipleline.Benchmark/PipelineBenchmarks.cs index f39bc7b..bd00d9e 100644 --- a/test/Hyperbee.Pipleline.Benchmark/PipelineBenchmarks.cs +++ b/test/Hyperbee.Pipleline.Benchmark/PipelineBenchmarks.cs @@ -12,34 +12,26 @@ namespace Hyperbee.Pipeline.Benchmark; public class PipelineBenchmarks { - - [Benchmark] - public void PipelineExecution() + private FunctionAsync _commandExecution; + private FunctionAsync _commandMiddleware; + private FunctionAsync _commandEnumeration; + private FunctionAsync _commandCancellation; + private FunctionAsync _commandAuth; + + [GlobalSetup] + public void Setup() { - var command = PipelineFactory + _commandExecution = PipelineFactory .Start() .Pipe( ( ctx, arg ) => int.Parse( arg ) ) .Build(); - command( new PipelineContext(), "5" ); - } - - [Benchmark] - public void PipelineMiddleware() - { - - var command = PipelineFactory + _commandMiddleware = PipelineFactory .Start() .HookAsync( async ( ctx, arg, next ) => await next( ctx, arg + "{" ) + "}" ) .Pipe( ( ctx, arg ) => arg + "1" ) .Build(); - command( new PipelineContext() ); - } - - [Benchmark] - public void PipelineEnumeration() - { var count = 0; var command1 = PipelineFactory @@ -48,7 +40,7 @@ public void PipelineEnumeration() .Pipe( ( ctx, arg ) => count += 10 ) .Build(); - var command = PipelineFactory + _commandEnumeration = PipelineFactory .Start() .Pipe( ( ctx, arg ) => arg.Split( ' ' ) ) .PipeAsync( async ( ctx, arg ) => @@ -61,13 +53,7 @@ public void PipelineEnumeration() .Pipe( ( ctx, arg ) => string.Join( ' ', arg ) ) .Build(); - command( new PipelineContext(), "e f" ); - } - - [Benchmark] - public void PipelineCancellation() - { - var command = PipelineFactory + _commandCancellation = PipelineFactory .Start() .Pipe( ( ctx, arg ) => 1 ) .Pipe( ( ctx, arg ) => @@ -78,7 +64,35 @@ public void PipelineCancellation() .Pipe( ( ctx, arg ) => 3 ) .Build(); - command( new PipelineContext() ); + _commandAuth = PipelineFactory + .Start() + .PipeIfClaim( new Claim( "Role", "reader" ), b => b.Pipe( Complex ) ) + .Build(); + + } + + [Benchmark] + public void PipelineExecution() + { + _commandExecution( new PipelineContext(), "5" ); + } + + [Benchmark] + public void PipelineMiddleware() + { + _commandMiddleware( new PipelineContext() ); + } + + [Benchmark] + public void PipelineEnumeration() + { + _commandEnumeration( new PipelineContext(), "e f" ); + } + + [Benchmark] + public void PipelineCancellation() + { + _commandCancellation( new PipelineContext() ); } [Benchmark] @@ -87,13 +101,7 @@ public void PipelineAuth() var factory = CreateContextFactory(); ILogger logger = null!; - - var command = PipelineFactory - .Start() - .PipeIfClaim( new Claim( "Role", "reader" ), b => b.Pipe( Complex ) ) - .Build(); - - command( factory.Create( logger ), "reader" ); + _commandAuth( factory.Create( logger ), "reader" ); } [Benchmark]