OperationBuilders
Purpose
Operation builders are the low-level primitives in Feber for generating per-property Expression fragments. They provide the foundation for higher-level builders like ActionBuilder and FunctionBuilder, which consume OperationExpressions to compile complete delegates.
Why Use This Approach?
This builder pattern is designed to improve runtime performance and developer productivity for repeated property-based operations. By automating the generation of property-based logic, you eliminate repetitive boilerplate code and avoid the overhead of reflection and manual expression construction on every invocation. The trade-off is a small upfront cost for building and caching the operation, which is quickly amortized when the operation is used many times.
- Performance: Compiled delegates execute much faster than repeated reflection or dynamic code generation.
- Scalability: The cost of building the operation is paid only once per type; subsequent invocations are as fast as a direct delegate call.
- Developer Productivity: This approach reduces code-bloat and improves developer productivity by removing repetitive property-handling logic from your codebase. Instead, you automate the generation of these operations, making your codebase cleaner, easier to maintain, and less error-prone.
Architectural Role
- Scans prototype types for public instance properties using SwissKnife helpers.
- Exposes
FilteredPropertiesandOperationExpressionsfor property-based expression generation. - Supplies stable
ParameterExpressioninstances for lambda compilation. - Used as the base for custom builders that need fine-grained control over property expression generation.
Core Types & Behaviors
OperationBuilderBase<TProto>: Scans properties, exposesFilteredPropertiesandOperationExpressions, and expects implementors to overrideBuildPropertyExpression.UnaryOperationBuilderBase<TProto, TInstance>: Supplies a stableInstanceParameterExpressionand expects implementors to overridePropertyExpression(PropertyInfo, ParameterExpression).BinaryOperationBuilderBase<TProto, TLeftInstance, TRightInstance>: SuppliesLeftInstanceParameterExpressionandRightInstanceParameterExpressionand expects implementors to overridePropertyExpression(PropertyInfo, ParameterExpression, ParameterExpression).
Usage Example: Null-Check Builder
using System;
using System.Linq.Expressions;
using System.Reflection;
using BrightSword.Feber.Core;
public sealed class NullCheckBuilder<T> : UnaryOperationBuilderBase<T, T>
{
protected override Expression PropertyExpression(PropertyInfo propertyInfo, ParameterExpression instanceParameter)
{
var propertyAccess = Expression.Property(instanceParameter, propertyInfo);
var nullConstant = Expression.Constant(null, propertyInfo.PropertyType);
return Expression.Equal(propertyAccess, nullConstant);
}
public Expression BuildAll() => Expression.Block(OperationExpressions);
}
// Usage
// var builder = new NullCheckBuilder<MyType>();
// var block = builder.BuildAll();
// var lam = Expression.Lambda<Action<MyType>>(block, builder.InstanceParameterExpression);
// var action = lam.Compile();
Best Practices
- ParameterExpression identity: Always use the builder’s provided parameter expressions. Do not create new ones inside overrides.
- Property scanning: Use SwissKnife’s
GetAllPropertiesfor robust property discovery. - Delegate composition: Higher-level builders (ActionBuilder/FunctionBuilder) consume
OperationExpressionsto compile delegates.
Customization
- Override
BuildPropertyExpressionor the more specialized property expression methods in unary/binary builders for custom logic. - Use the protected properties for parameter expressions to ensure correct lambda compilation.
See Also
- ActionBuilder.md — for composing side-effecting delegates.
- FunctionBuilder.md — for folding/aggregation patterns.