using System; using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Svrnty.CQRS.Abstractions.Attributes; namespace Svrnty.CQRS.Abstractions.Discovery; public sealed class CommandMeta : ICommandMeta { private readonly string _name; private readonly string _lowerCamelCaseName; public CommandMeta(Type commandType, Type serviceType, Type commandResultType) { CommandType = commandType; ServiceType = serviceType; CommandResultType = commandResultType; // Cache reflection and computed values once in constructor var nameAttribute = commandType.GetCustomAttribute(); _name = nameAttribute?.Name ?? commandType.Name.Replace("Command", string.Empty); _lowerCamelCaseName = ComputeLowerCamelCaseName(_name); // Build compiled delegate for handler invocation CompiledInvoker = BuildCompiledInvoker(serviceType, commandType, commandResultType); } public CommandMeta(Type commandType, Type serviceType) { CommandType = commandType; ServiceType = serviceType; // Cache reflection and computed values once in constructor var nameAttribute = commandType.GetCustomAttribute(); _name = nameAttribute?.Name ?? commandType.Name.Replace("Command", string.Empty); _lowerCamelCaseName = ComputeLowerCamelCaseName(_name); // Build compiled delegate for handler invocation CompiledInvoker = BuildCompiledInvoker(serviceType, commandType, null); } private static string ComputeLowerCamelCaseName(string name) { if (string.IsNullOrEmpty(name)) return name; var firstLetter = char.ToLowerInvariant(name[0]); return $"{firstLetter}{name.Substring(1)}"; } private static Func> BuildCompiledInvoker( Type serviceType, Type commandType, Type? resultType) { // Parameters: (object handler, object command, CancellationToken ct) var handlerParam = Expression.Parameter(typeof(object), "handler"); var commandParam = Expression.Parameter(typeof(object), "command"); var ctParam = Expression.Parameter(typeof(CancellationToken), "ct"); // Cast handler to actual handler type var handlerCast = Expression.Convert(handlerParam, serviceType); // Cast command to actual command type var commandCast = Expression.Convert(commandParam, commandType); // Get HandleAsync method with correct return type var expectedReturnType = resultType == null ? typeof(Task) : typeof(Task<>).MakeGenericType(resultType); var handleMethod = serviceType.GetMethod("HandleAsync", BindingFlags.Public | BindingFlags.Instance, null, new[] { commandType, typeof(CancellationToken) }, null); if (handleMethod == null || handleMethod.ReturnType != expectedReturnType) throw new InvalidOperationException($"HandleAsync method with return type {expectedReturnType} not found on {serviceType}"); // Call handler.HandleAsync(command, ct) var methodCall = Expression.Call(handlerCast, handleMethod, commandCast, ctParam); Expression invokeCall; if (resultType == null) { // Command without result: Task HandleAsync(...) var asyncHelperMethod = typeof(CommandMeta).GetMethod(nameof(InvokeAndReturnNull), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)!; invokeCall = Expression.Call(asyncHelperMethod, methodCall); } else { // Command with result: Task HandleAsync(...) var asyncHelperMethod = typeof(CommandMeta).GetMethod(nameof(InvokeAndBox), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)! .MakeGenericMethod(resultType); invokeCall = Expression.Call(asyncHelperMethod, methodCall); } // Compile to delegate var lambda = Expression.Lambda>>( invokeCall, handlerParam, commandParam, ctParam); return lambda.Compile(); } private static async Task InvokeAndReturnNull(Task task) { await task; return null; } private static async Task InvokeAndBox(Task task) { var result = await task; return result; } public string Name => _name; public Type CommandType { get; } public Type ServiceType { get; } public Type CommandResultType { get; } public string LowerCamelCaseName => _lowerCamelCaseName; /// /// Compiled delegate for invoking the handler without reflection. /// Signature: (object handler, object command, CancellationToken ct) => Task /// public Func> CompiledInvoker { get; } }