using System.Linq.Expressions;
using System.Collections.Generic;
public string Name { get; set; }
public decimal Price { get; set; }
public bool Enabled { get; set; }
public static Expression<Func<Package, bool>> IsActive =>
public DateTimeOffset Start { get; set; }
public DateTimeOffset End { get; set; }
public bool Enabled { get; set; }
public static Expression<Func<Schedule, bool>> IsActive =>
x => x.Enabled && x.Start > DateTimeOffset.Now;
public Package Package { get; init; }
public Schedule Schedule { get; init; }
static Lazy<Expression<Func<SchedulePackage, bool>>> IsActiveExpression = new(static () => {
var left = Package.IsActive.Compose((SchedulePackage sp) => sp.Package);
var right = Schedule.IsActive.Compose((SchedulePackage sp) => sp.Schedule);
Expression<Func<bool, bool, bool>> binary = (b1, b2) => b1 && b2;
return binary.Compose(left, right);
public static Expression<Func<SchedulePackage, bool>> IsActive => IsActiveExpression.Value;
public static partial class ExpressionExtensions
public static Expression<Func<T1, TResult>> Compose<T1, T2, TResult>(this Expression<Func<T2, TResult>> outer, Expression<Func<T1, T2>> inner) =>
Expression.Lambda<Func<T1, TResult>>(
new ParameterReplacer((outer.Parameters[0], inner.Body)).Visit(outer.Body),
false, inner.Parameters[0]);
public static Expression<Func<T1, TResult>> Compose<T1, T2, T3, TResult>(this Expression<Func<T2, T3, TResult>> outer, Expression<Func<T1, T2>> inner1, Expression<Func<T1, T3>> inner2)
var inner2body = new ParameterReplacer((inner2.Parameters[0], (Expression)inner1.Parameters[0])).Visit(inner2.Body);
return Expression.Lambda<Func<T1, TResult>>(
new ParameterReplacer((outer.Parameters[0], inner1.Body), (outer.Parameters[1], inner2body)).Visit(outer.Body),
false, inner1.Parameters[0]);
class ParameterReplacer : ExpressionVisitor
readonly Dictionary<ParameterExpression, Expression> parametersToReplace;
public ParameterReplacer(params (ParameterExpression parameter, Expression replacement) [] parametersToReplace) =>
this.parametersToReplace = parametersToReplace.ToDictionary(p => p.parameter, p => p.replacement);
protected override Expression VisitParameter(ParameterExpression p) =>
parametersToReplace.TryGetValue(p, out var e) ? e : base.VisitParameter(p);
public static void Main()
Console.WriteLine($"Schedule.IsActive: {Schedule.IsActive}");
Console.WriteLine($"Package.IsActive : {Package.IsActive}");
Console.WriteLine($"SchedulePackage.IsActive: {SchedulePackage.IsActive}");
Assert.That(!SchedulePackage.IsActive.ToString().Contains("Invoke("));
var schedulePackages = new [] { true, false }
.SelectMany(b => new [] { true, false }, (b1, b2) => (b1, b2))
new SchedulePackage { Schedule = new () { Enabled = p.b1, Start = DateTime.Now.AddDays(4) }, Package = new () { Enabled = p.b2 } })
var items = schedulePackages.Where(SchedulePackage.IsActive);
Console.WriteLine(items.Count());
Assert.AreEqual(1, items.Count());
var isActive = SchedulePackage.IsActive.Compile();
foreach (var enabled1 in new [] { true, false } )
foreach (var enabled2 in new [] { true, false } )
Test(new () { Enabled = enabled1 }, new () { Enabled = enabled2, Start = DateTime.Now.AddDays(4) }, isActive);
static void Test(Package p, Schedule s, Func<SchedulePackage, bool> isActive)
var schedulePackage = new SchedulePackage { Schedule = s, Package = p };
Console.WriteLine($"isActive(schedulePackage) = {isActive(schedulePackage)}");
Assert.AreEqual(Package.IsActive.Compile()(p) && Schedule.IsActive.Compile()(s), isActive(schedulePackage));