引言
在 .NET 开发中,流程编排是一个常见需求:将多个处理步骤组合成一条流水线,并支持同步/异步执行、环境感知、动态配置等特性。SkeletonFlow 是我开发的一个轻量级框架,它以“骨架”(Skeleton)为基本单元,通过组合的方式构建可复用、可观测的数据处理流程。虽然框架在表面形式上(如 Map、Filter、Then 组合)与函数式编程有一些相似之处,但其设计理念、核心机制和实现语言(C#)的范式决定了它本质上是一个面向对象的框架。本文将从多个维度对比 SkeletonFlow 与函数式编程的异同,并通过一个具体实例展示两者的区别,帮助读者理解两者的本质差异。
SkeletonFlow 概览
SkeletonFlow 的核心是 ISkeleton<TInput, TOutput> 接口,它定义了同步和异步执行方法。开发者可以通过继承 SkeletonBase 或使用 DelegateSkeleton 快速创建骨架。内置的 BuiltInSkeletons 类提供了常用的集合操作(如 Filter、Map、Reduce)。骨架可以通过 Then、Branch、Loop、Parallel 等扩展方法进行组合,形成复杂的处理流程。此外,框架还支持动态配置解析(JSON)、依赖注入和上下文传递(SkeletonContext),使其更适合实际企业级应用。
与函数式编程的相同点
尽管 SkeletonFlow 并非受函数式编程启发,但在设计上确实与函数式思想有一些自然的交集:
函数组合思想
Then 扩展方法将两个骨架连接成一个新骨架,类似于数学上的函数复合 f ∘ g。组合后的骨架可以像函数一样应用于输入并产生输出。
高阶操作
内置骨架如 Map、Filter、Reduce 接受委托作为参数,这与函数式语言中的高阶函数(map、filter、fold)在概念上一一对应。
声明式流程构建
通过组合子(Then、Branch、Loop)描述执行逻辑,而非手写命令式控制流,这符合函数式编程中“组合优于继承”的风格。
类型安全
利用 C# 泛型确保骨架的输入输出类型匹配,组合时进行类型检查,与静态类型函数式语言一致。
倾向于不可变数据流
内置的 Map、Filter 等操作返回新的集合,不修改原始输入,体现了函数式数据不变性的倾向。
与函数式编程的核心不同点
然而,上述相似之处只是表象。深入框架的设计和实现,会发现它与函数式编程有本质区别:

实例对比:整数列表处理
为了更直观地说明差异,我们以一个经典的整数列表处理为例:输入 [1,2,3,4,5],过滤出偶数,平方,然后求和。
使用 SkeletonFlow
// 1. 定义可复用的骨架(每个骨架有名称)
var filterEven = BuiltInSkeletons.Filter<int>("FilterEven", x => x % 2 == 0);
var square = BuiltInSkeletons.Map<int, int>("Square", x => x * x);
var sum = BuiltInSkeletons.Reduce<int, int>("Sum", 0, (acc, x) => acc + x);
// 2. 组合成流水线骨架
var pipeline = filterEven.Then(square).Then(sum);
// 3. 执行
var input = new List<int> { 1, 2, 3, 4, 5 };
var result = pipeline.Execute(input); // 输出 20
使用函数式风格(C# LINQ)
var input = new List<int> { 1, 2, 3, 4, 5 };
var result = input
.Where(x => x % 2 == 0)
.Select(x => x * x)
.Sum(); // 输出 20
核心差异分析:
单元粒度:SkeletonFlow 中每一步都是独立的骨架对象,拥有名称和描述,可以在多个流水线中复用;而 LINQ 中每一步是匿名操作符,没有独立标识。
组合方式:SkeletonFlow 显式使用 Then 连接,强调流程的显式组合;LINQ 是自然的方法链,操作符直接叠加。
状态交互:SkeletonFlow 的骨架可以在 SkeletonContext 中读写共享数据(例如记录日志、传递中间结果);LINQ 操作默认是无状态的,若需共享状态通常要捕获外部变量,可能引入副作用。
执行环境:SkeletonFlow 的 Execute 方法接受可选的 SkeletonContext,可以传递取消令牌、服务提供者等;LINQ 执行不涉及外部上下文。
这个例子清晰地表明:虽然两者都能实现相同的功能,但 SkeletonFlow 更强调流程的可管理性、可观测性和环境集成,而函数式风格(LINQ)更注重简洁的数据变换和纯函数组合。
结论
SkeletonFlow 是一个面向对象的流程编排框架,它借用了函数式编程中的“组合”思想,但本质上是基于对象、允许可变状态、使用异常处理、深度集成 .NET 生态的实用工具。它的设计目标是为企业级应用提供可复用、可观测、动态可配置的流程构建能力,而非追求纯函数式特性。因此,SkeletonFlow 与函数式编程既有表面相似之处,又有根本性的差异。开发者可以根据项目需求选择适合的范式:如果需要严格的可推导性、无副作用和数学保证,函数式编程是更好的选择;如果需要灵活的环境集成、动态配置和与现有 .NET 基础设施无缝协作,SkeletonFlow 则提供了一个实用的解决方案。
namespace SkeletonFlow
{
#nullable disable
using ContextManagement;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions; // OPTIMIZED: 用于表达式树编译
using System.Threading;
using System.Threading.Tasks;
#region Core
public interface ISkeleton { }
public class SkeletonContext : IDisposable
{
private readonly IContext _context;
private readonly bool _ownsContext;
private CancellationTokenSource _linkedCts;
private readonly ConcurrentDictionary<string, object> _items;
private readonly object _lock = new();
private bool _disposed;
private const string ItemsKey = "__SkeletonContextItems";
public IServiceProvider ServiceProvider { get; set; }
public IDictionary<string, object> Items => _items;
public CancellationToken CancellationToken
{
get => _linkedCts.Token;
set
{
lock (_lock)
{
if (value == _linkedCts.Token) return;
_linkedCts.Cancel();
_linkedCts.Dispose();
_linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_context.CancellationToken, value);
}
}
}
public SkeletonContext(IContext context = null)
{
_context = context ?? new ConcreteContext();
_ownsContext = context == null;
_linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_context.CancellationToken);
_items = _context.TryGetData<ConcurrentDictionary<string, object>>(out var d, ItemsKey) ? d : new();
_context.SetData(_items, ItemsKey);
}
public void Dispose()
{
lock (_lock)
{
if (_disposed) return;
_linkedCts.Dispose();
if (_ownsContext) _context.Dispose();
else _context.RemoveData<ConcurrentDictionary<string, object>>(ItemsKey);
_disposed = true;
}
}
}
public interface ISkeleton<in TInput, TOutput> : ISkeleton
{
string Name { get; }
string Description { get; }
TOutput Execute(TInput input, SkeletonContext context = null);
Task<TOutput> ExecuteAsync(TInput input, SkeletonContext context = null);
}
public abstract class SkeletonBase<TInput, TOutput> : ISkeleton<TInput, TOutput>
{
public string Name { get; }
public string Description { get; }
protected SkeletonBase(string name = null, string description = null) =>
(Name, Description) = (name ?? GetType().Name, description);
public TOutput Execute(TInput input, SkeletonContext context = null)
{
bool ownsContext = context == null;
context ??= new SkeletonContext();
try
{
context.CancellationToken.ThrowIfCancellationRequested();
return ExecuteCore(input, context);
}
finally
{
if (ownsContext) context.Dispose();
}
}
public async Task<TOutput> ExecuteAsync(TInput input, SkeletonContext context = null)
{
bool ownsContext = context == null;
context ??= new SkeletonContext();
try
{
context.CancellationToken.ThrowIfCancellationRequested();
return await ExecuteCoreAsync(input, context).ConfigureAwait(false);
}
finally
{
if (ownsContext) context.Dispose();
}
}
protected abstract TOutput ExecuteCore(TInput input, SkeletonContext context);
protected abstract Task<TOutput> ExecuteCoreAsync(TInput input, SkeletonContext context);
}
public class DelegateSkeleton<TInput, TOutput> : SkeletonBase<TInput, TOutput>
{
private readonly Func<TInput, SkeletonContext, TOutput> _sync;
private readonly Func<TInput, SkeletonContext, Task<TOutput>> _async;
public DelegateSkeleton(string name,
Func<TInput, TOutput> sync = null,
Func<TInput, Task<TOutput>> async = null,
string description = null) : this(name,
sync == null ? null : (i, _) => sync(i),
async == null ? null : (i, _) => async(i),
description)
{ }
public DelegateSkeleton(string name,
Func<TInput, SkeletonContext, TOutput> syncWithCtx = null,
Func<TInput, SkeletonContext, Task<TOutput>> asyncWithCtx = null,
string description = null) : base(name, description) =>
(_sync, _async) = (syncWithCtx, asyncWithCtx);
protected override TOutput ExecuteCore(TInput input, SkeletonContext ctx) =>
_sync != null ? _sync(input, ctx) :
_async != null ? throw new NotSupportedException("同步执行需要提供同步委托,当前只有异步委托。请提供同步委托或使用 ExecuteAsync。") :
throw new NotSupportedException("没有提供任何执行委托。");
protected override async Task<TOutput> ExecuteCoreAsync(TInput input, SkeletonContext ctx) =>
_async != null ? await _async(input, ctx).ConfigureAwait(false) :
await Task.Run(() => ExecuteCore(input, ctx), ctx.CancellationToken).ConfigureAwait(false);
}
#endregion
#region BuiltIn
public static class BuiltInSkeletons
{
private static ISkeleton<TInput, TOutput> Create<TInput, TOutput>(string name, string desc,
Func<TInput, SkeletonContext, TOutput> sync) =>
new DelegateSkeleton<TInput, TOutput>(name, sync,
(i, ctx) => Task.Run(() => sync(i, ctx), ctx.CancellationToken), desc);
public static ISkeleton<IEnumerable<T>, IEnumerable<T>> Filter<T>(string name, Func<T, bool> predicate, string desc = null) =>
Create<IEnumerable<T>, IEnumerable<T>>(name, desc, (src, ctx) =>
{
var list = new List<T>();
foreach (var i in src) { ctx.CancellationToken.ThrowIfCancellationRequested(); if (predicate(i)) list.Add(i); }
return list;
});
public static ISkeleton<IEnumerable<T>, IEnumerable<TResult>> Map<T, TResult>(string name, Func<T, TResult> selector, string desc = null) =>
Create<IEnumerable<T>, IEnumerable<TResult>>(name, desc, (src, ctx) =>
{
var list = new List<TResult>();
foreach (var i in src) { ctx.CancellationToken.ThrowIfCancellationRequested(); list.Add(selector(i)); }
return list;
});
public static ISkeleton<IEnumerable<T>, TAccumulate> Reduce<T, TAccumulate>(string name, TAccumulate seed, Func<TAccumulate, T, TAccumulate> acc, string desc = null) =>
Create<IEnumerable<T>, TAccumulate>(name, desc, (src, ctx) =>
{
var r = seed;
foreach (var i in src) { ctx.CancellationToken.ThrowIfCancellationRequested(); r = acc(r, i); }
return r;
});
public static ISkeleton<IEnumerable<T>, IEnumerable<T>> Skip<T>(string name, int count, string desc = null) =>
Create<IEnumerable<T>, IEnumerable<T>>(name, desc, (src, ctx) => src.Skip(count).ToList());
public static ISkeleton<IEnumerable<T>, IEnumerable<T>> Take<T>(string name, int count, string desc = null) =>
Create<IEnumerable<T>, IEnumerable<T>>(name, desc, (src, ctx) => src.Take(count).ToList());
public static ISkeleton<IEnumerable<T>, IEnumerable<T>> Distinct<T>(string name, IEqualityComparer<T> comparer = null, string desc = null) =>
Create<IEnumerable<T>, IEnumerable<T>>(name, desc, (src, ctx) => src.Distinct(comparer).ToList());
public static ISkeleton<IEnumerable<T>, IEnumerable<IGrouping<TKey, T>>> GroupBy<T, TKey>(string name, Func<T, TKey> keySelector, IEqualityComparer<TKey> comparer = null, string desc = null) =>
Create<IEnumerable<T>, IEnumerable<IGrouping<TKey, T>>>(name, desc, (src, ctx) => src.GroupBy(keySelector, comparer).ToList());
public static ISkeleton<IEnumerable<T>, IOrderedEnumerable<T>> OrderBy<T, TKey>(string name, Func<T, TKey> keySelector, bool descending = false, IComparer<TKey> comparer = null, string desc = null) =>
Create<IEnumerable<T>, IOrderedEnumerable<T>>(name, desc, (src, ctx) => descending ? src.OrderByDescending(keySelector, comparer) : src.OrderBy(keySelector, comparer));
public static ISkeleton<IEnumerable<T>, T> First<T>(string name, Func<T, bool> predicate = null, string desc = null) =>
Create<IEnumerable<T>, T>(name, desc, (src, ctx) => predicate == null ? src.First() : src.First(predicate));
public static ISkeleton<IEnumerable<T>, T> Single<T>(string name, Func<T, bool> predicate = null, string desc = null) =>
Create<IEnumerable<T>, T>(name, desc, (src, ctx) => predicate == null ? src.Single() : src.Single(predicate));
public static ISkeleton<IEnumerable<T>, List<T>> ToList<T>(string name, string desc = null) =>
Create<IEnumerable<T>, List<T>>(name, desc, (src, ctx) => src.ToList());
public static ISkeleton<IEnumerable<T>, T[]> ToArray<T>(string name, string desc = null) =>
Create<IEnumerable<T>, T[]>(name, desc, (src, ctx) => src.ToArray());
public static ISkeleton<IEnumerable<T>, IEnumerable<T>> ParallelForEach<T>(string name, Func<T, T> transform, int dop = –1, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, IEnumerable<T>>(name,
syncWithCtx: (src, ctx) =>
{
var list = src.ToList();
var results = new T[list.Count];
Parallel.For(0, list.Count, new() { CancellationToken = ctx.CancellationToken, MaxDegreeOfParallelism = dop }, i => results[i] = transform(list[i]));
return results;
},
asyncWithCtx: async (src, ctx) =>
{
var list = src.ToList();
var results = new T[list.Count];
using var sem = new SemaphoreSlim(dop > 0 ? dop : int.MaxValue);
await Task.WhenAll(list.Select(async (item, i) =>
{
await sem.WaitAsync(ctx.CancellationToken).ConfigureAwait(false);
try { ctx.CancellationToken.ThrowIfCancellationRequested(); results[i] = transform(item); }
finally { sem.Release(); }
})).ConfigureAwait(false);
return results;
}, desc);
}
#endregion
#region Composition
public static class SkeletonExtensions
{
public static ISkeleton<TInput, TOutput> Then<TInput, TIntermediate, TOutput>(
this ISkeleton<TInput, TIntermediate> first,
ISkeleton<TIntermediate, TOutput> next) =>
first == null ? throw new ArgumentNullException(nameof(first)) :
next == null ? throw new ArgumentNullException(nameof(next)) :
new DelegateSkeleton<TInput, TOutput>($"{first.Name}+{next.Name}",
(i, ctx) => next.Execute(first.Execute(i, ctx), ctx),
async (i, ctx) => await next.ExecuteAsync(await first.ExecuteAsync(i, ctx).ConfigureAwait(false), ctx).ConfigureAwait(false),
$"Sequential {first.Description} & {next.Description}");
public static ISkeleton<TInput, TOutput> Branch<TInput, TOutput>(
this Func<TInput, bool> cond,
ISkeleton<TInput, TOutput> trueBranch,
ISkeleton<TInput, TOutput> falseBranch) =>
cond == null ? throw new ArgumentNullException(nameof(cond)) :
new DelegateSkeleton<TInput, bool>("BranchCond", (i, _) => cond(i), (i, _) => Task.FromResult(cond(i)))
.Branch(trueBranch, falseBranch);
public static ISkeleton<TInput, TOutput> Branch<TInput, TOutput>(
this ISkeleton<TInput, bool> condSkel,
ISkeleton<TInput, TOutput> trueBranch,
ISkeleton<TInput, TOutput> falseBranch) =>
condSkel == null ? throw new ArgumentNullException(nameof(condSkel)) :
trueBranch == null ? throw new ArgumentNullException(nameof(trueBranch)) :
falseBranch == null ? throw new ArgumentNullException(nameof(falseBranch)) :
new DelegateSkeleton<TInput, TOutput>("Branch",
(i, ctx) => condSkel.Execute(i, ctx) ? trueBranch.Execute(i, ctx) : falseBranch.Execute(i, ctx),
async (i, ctx) => await condSkel.ExecuteAsync(i, ctx).ConfigureAwait(false)
? await trueBranch.ExecuteAsync(i, ctx) : await falseBranch.ExecuteAsync(i, ctx));
public static ISkeleton<TInput, TInput> Loop<TInput>(
this Func<TInput, bool> cond,
ISkeleton<TInput, TInput> body) =>
cond == null ? throw new ArgumentNullException(nameof(cond)) :
new DelegateSkeleton<TInput, bool>("LoopCond", (i, _) => cond(i), (i, _) => Task.FromResult(cond(i)))
.Loop(body);
public static ISkeleton<TInput, TInput> Loop<TInput>(
this ISkeleton<TInput, bool> condSkel,
ISkeleton<TInput, TInput> body) =>
condSkel == null ? throw new ArgumentNullException(nameof(condSkel)) :
body == null ? throw new ArgumentNullException(nameof(body)) :
new DelegateSkeleton<TInput, TInput>("Loop",
(i, ctx) =>
{
var cur = i;
while (condSkel.Execute(cur, ctx)) cur = body.Execute(cur, ctx);
return cur;
},
async (i, ctx) =>
{
var cur = i;
while (await condSkel.ExecuteAsync(cur, ctx).ConfigureAwait(false))
cur = await body.ExecuteAsync(cur, ctx).ConfigureAwait(false);
return cur;
});
// OPTIMIZED: 同步版改用 Parallel.Invoke 减少线程调度开销
public static ISkeleton<TInput, (TOutput1, TOutput2)> Parallel<TInput, TOutput1, TOutput2>(
this ISkeleton<TInput, TOutput1> s1,
ISkeleton<TInput, TOutput2> s2)
{
if (s1 == null) throw new ArgumentNullException(nameof(s1));
if (s2 == null) throw new ArgumentNullException(nameof(s2));
return new DelegateSkeleton<TInput, (TOutput1, TOutput2)>("Parallel",
syncWithCtx: (i, ctx) =>
{
TOutput1 r1 = default!;
TOutput2 r2 = default!;
System.Threading.Tasks.Parallel.Invoke(
new System.Threading.Tasks.ParallelOptions { CancellationToken = ctx.CancellationToken },
() => r1 = s1.Execute(i, ctx),
() => r2 = s2.Execute(i, ctx)
);
return (r1, r2);
},
asyncWithCtx: async (i, ctx) =>
{
var t1 = s1.ExecuteAsync(i, ctx);
var t2 = s2.ExecuteAsync(i, ctx);
return (await t1.ConfigureAwait(false), await t2.ConfigureAwait(false));
});
}
public static ISkeleton<IEnumerable<TInput>, IEnumerable<TOutput>> ForEach<TInput, TOutput>(
this ISkeleton<TInput, TOutput> element) =>
element == null ? throw new ArgumentNullException(nameof(element)) :
new DelegateSkeleton<IEnumerable<TInput>, IEnumerable<TOutput>>($"ForEach({element.Name})",
(src, ctx) => src.Select(i => element.Execute(i, ctx)).ToList(),
async (src, ctx) =>
{
var r = new List<TOutput>();
foreach (var i in src) r.Add(await element.ExecuteAsync(i, ctx).ConfigureAwait(false));
return r;
});
public static ISkeleton<IEnumerable<TInput>, IEnumerable<TOutput>> SelectMany<TInput, TOutput>(
this ISkeleton<TInput, IEnumerable<TOutput>> element) =>
element == null ? throw new ArgumentNullException(nameof(element)) :
new DelegateSkeleton<IEnumerable<TInput>, IEnumerable<TOutput>>($"SelectMany({element.Name})",
(src, ctx) => src.SelectMany(i => element.Execute(i, ctx)).ToList(),
async (src, ctx) =>
{
var r = new List<TOutput>();
foreach (var i in src) r.AddRange(await element.ExecuteAsync(i, ctx).ConfigureAwait(false));
return r;
});
}
#endregion
#region Dynamic
public class SkeletonConfig
{
public string Name { get; set; }
public string Type { get; set; }
public Dictionary<string, object> Parameters { get; set; } = new();
public List<SkeletonConfig> Children { get; set; } = new();
}
public interface ICompositeSkeleton : ISkeleton
{
void SetChildren(IEnumerable<ISkeleton> children);
IEnumerable<ISkeleton> GetChildren();
}
public interface IOperationProvider
{
TDelegate GetOperation<TDelegate>(string name, params object[] parameters) where TDelegate : Delegate;
}
public class DictionaryOperationProvider : IOperationProvider
{
private readonly ConcurrentDictionary<string, Func<object[], object>> _factories = new();
public void Register<TDelegate>(string name, TDelegate op) where TDelegate : Delegate => _factories[name] = _ => op;
public void RegisterFactory<TDelegate>(string name, Func<object[], TDelegate> factory) where TDelegate : Delegate => _factories[name] = args => factory(args);
public TDelegate GetOperation<TDelegate>(string name, params object[] parameters) where TDelegate : Delegate =>
_factories.TryGetValue(name, out var f) && f(parameters) is TDelegate d ? d : throw new InvalidOperationException($"Operation '{name}' not found.");
}
public interface ISkeletonRegistry
{
void Register<TSkeleton>() where TSkeleton : class, ISkeleton;
void Register(string typeName, Func<object[], ISkeleton> factory);
ISkeleton Resolve(SkeletonConfig config, IServiceProvider services = null);
}
// OPTIMIZED: 重构 DefaultSkeletonRegistry,增加类型扫描缓存、构造函数缓存
public class DefaultSkeletonRegistry : ISkeletonRegistry
{
private readonly ConcurrentDictionary<string, Type> _typeCache = new();
private readonly ConcurrentDictionary<string, byte> _negativeCache = new();
private readonly ConcurrentDictionary<string, Func<object[], ISkeleton>> _factories = new();
// 新增缓存
private readonly ConcurrentDictionary<Type, ConstructorInfo> _bestCtorCache = new();
private readonly ConcurrentDictionary<Type, ParameterInfo[]> _ctorParamsCache = new();
private readonly Lazy<Dictionary<string, List<Type>>> _typesByName;
public System.Text.Json.JsonSerializerOptions JsonOptions { get; set; } = new() { PropertyNameCaseInsensitive = true };
public DefaultSkeletonRegistry()
{
// Lazy 扫描所有程序集中的类型,按名称分组
_typesByName = new Lazy<Dictionary<string, List<Type>>>(() =>
{
var dict = new Dictionary<string, List<Type>>(StringComparer.OrdinalIgnoreCase);
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
foreach (var type in asm.GetTypes())
{
if (!dict.TryGetValue(type.Name, out var list))
dict[type.Name] = list = new List<Type>();
list.Add(type);
// 同时用全名注册
if (!dict.ContainsKey(type.FullName!))
dict[type.FullName!] = new List<Type> { type };
}
}
catch { }
}
return dict;
}, LazyThreadSafetyMode.ExecutionAndPublication);
}
public void Register<TSkeleton>() where TSkeleton : class, ISkeleton =>
Register(typeof(TSkeleton).Name, args => (TSkeleton)Activator.CreateInstance(typeof(TSkeleton), args));
public void Register(string typeName, Func<object[], ISkeleton> factory) => _factories[typeName] = factory;
public ISkeleton Resolve(SkeletonConfig config, IServiceProvider services = null)
{
if (_factories.TryGetValue(config.Type, out var factory))
{
var skel = factory(ResolveParams(config.Parameters, null, services));
if (skel is ICompositeSkeleton comp && config.Children?.Count > 0)
comp.SetChildren(config.Children.Select(c => Resolve(c, services)));
return skel;
}
var type = ResolveType(config.Type) ?? throw new InvalidOperationException($"Skeleton type '{config.Type}' not found.");
var ctorArgs = ResolveParams(config.Parameters, type, services);
var instance = Activator.CreateInstance(type, ctorArgs) as ISkeleton ?? throw new InvalidOperationException($"Type '{config.Type}' does not implement ISkeleton.");
if (instance is ICompositeSkeleton comp2 && config.Children?.Count > 0)
comp2.SetChildren(config.Children.Select(c => Resolve(c, services)));
return instance;
}
// 优化后的参数解析,使用缓存的构造函数
private object[] ResolveParams(IDictionary<string, object> parameters, Type typeHint, IServiceProvider services)
{
if (parameters == null || parameters.Count == 0) return Array.Empty<object>();
if (typeHint == null)
throw new InvalidOperationException("Cannot resolve parameters without type hint.");
// 获取或选择最佳构造函数
if (!_bestCtorCache.TryGetValue(typeHint, out var ctor))
{
ctor = SelectBestConstructor(typeHint, parameters.Keys);
_bestCtorCache[typeHint] = ctor;
}
var ctorParams = _ctorParamsCache.GetOrAdd(typeHint, t => ctor.GetParameters());
var args = new object[ctorParams.Length];
for (int i = 0; i < ctorParams.Length; i++)
{
var p = ctorParams[i];
if (parameters.TryGetValue(p.Name, out var val))
{
args[i] = ResolveValue(val, p.ParameterType, services);
}
else if (services != null && p.ParameterType.IsClass && p.ParameterType != typeof(string))
{
var service = services.GetService(p.ParameterType);
if (service != null)
args[i] = service;
else if (p.HasDefaultValue)
args[i] = p.DefaultValue;
else
throw new InvalidOperationException($"No value for parameter '{p.Name}' and no service registered.");
}
else if (p.HasDefaultValue)
{
args[i] = p.DefaultValue;
}
else
{
throw new InvalidOperationException($"No value provided for parameter '{p.Name}'.");
}
}
return args;
}
// 根据参数名选择匹配的构造函数
private ConstructorInfo SelectBestConstructor(Type type, IEnumerable<string> parameterNames)
{
var constructors = type.GetConstructors().OrderByDescending(c => c.GetParameters().Length).ToList();
var nameSet = new HashSet<string>(parameterNames, StringComparer.OrdinalIgnoreCase);
foreach (var ctor in constructors)
{
bool allMatch = true;
foreach (var p in ctor.GetParameters())
{
if (!nameSet.Contains(p.Name) && !p.HasDefaultValue)
{
allMatch = false;
break;
}
}
if (allMatch) return ctor;
}
return constructors.FirstOrDefault()
?? throw new InvalidOperationException($"No public constructor for type {type.Name}");
}
private object ResolveValue(object val, Type target, IServiceProvider services) => val switch
{
string s when s.StartsWith("$service:") => ResolveService(s[9..], services),
System.Text.Json.JsonElement je => target != null ? System.Text.Json.JsonSerializer.Deserialize(je, target, JsonOptions) : je.ValueKind switch
{
System.Text.Json.JsonValueKind.String => je.GetString(),
System.Text.Json.JsonValueKind.Number => je.TryGetInt32(out int i) ? i : je.TryGetInt64(out long l) ? l : je.GetDouble(),
System.Text.Json.JsonValueKind.True => true,
System.Text.Json.JsonValueKind.False => false,
System.Text.Json.JsonValueKind.Null => null,
System.Text.Json.JsonValueKind.Object => System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(je, JsonOptions),
System.Text.Json.JsonValueKind.Array => System.Text.Json.JsonSerializer.Deserialize<object[]>(je, JsonOptions),
_ => je.ToString()
},
_ => val
};
private object ResolveService(string serviceRef, IServiceProvider services)
{
var type = ResolveType(serviceRef);
if (type == null) throw new InvalidOperationException($"Service type '{serviceRef}' not found.");
var service = services?.GetService(type);
if (service == null) throw new InvalidOperationException($"Service '{serviceRef}' not found.");
return service;
}
// 优化后的类型解析,使用预扫描缓存
private Type ResolveType(string name)
{
if (_typeCache.TryGetValue(name, out var t)) return t;
if (_negativeCache.ContainsKey(name)) return null;
t = Type.GetType(name); // 处理程序集限定名
if (t != null)
{
_typeCache[name] = t;
return t;
}
if (_typesByName.Value.TryGetValue(name, out var matches))
{
if (matches.Count == 0)
{
_negativeCache[name] = 1;
return null;
}
if (matches.Count > 1)
throw new InvalidOperationException($"Ambiguous type name '{name}'. Found multiple: {string.Join(", ", matches.Select(t => t.AssemblyQualifiedName))}");
t = matches[0];
_typeCache[name] = t;
return t;
}
_negativeCache[name] = 1;
return null;
}
}
public interface IConfigParser
{
SkeletonConfig Parse(string configText);
}
public class JsonConfigParser : IConfigParser
{
private readonly System.Text.Json.JsonSerializerOptions _options;
public JsonConfigParser(System.Text.Json.JsonSerializerOptions options = null) =>
_options = options ?? new() { PropertyNameCaseInsensitive = true, AllowTrailingCommas = true };
public SkeletonConfig Parse(string config) =>
System.Text.Json.JsonSerializer.Deserialize<SkeletonConfig>(config, _options) ?? throw new InvalidOperationException("Invalid config");
}
#endregion
#region Adapters
public interface IExecutionAdapter<TEnvironment>
{
TInput ExtractInput<TInput>(TEnvironment env, SkeletonContext context);
void ApplyOutput<TOutput>(TEnvironment env, TOutput output, SkeletonContext context);
Task ApplyOutputAsync<TOutput>(TEnvironment env, TOutput output, SkeletonContext context);
}
#endregion
#region Engine
public class SkeletonEngine
{
private readonly ISkeletonRegistry _registry;
private readonly IConfigParser _parser;
public SkeletonEngine(ISkeletonRegistry registry, IConfigParser parser) => (_registry, _parser) = (registry, parser);
public Task<TOutput> ExecuteAsync<TInput, TOutput>(string configText, TInput input, SkeletonContext context = null) =>
ExecuteAsync<TInput, TOutput>(_parser.Parse(configText), input, context);
public async Task<TOutput> ExecuteAsync<TInput, TOutput>(SkeletonConfig config, TInput input, SkeletonContext context = null)
{
context ??= new();
var skel = _registry.Resolve(config, context.ServiceProvider) as ISkeleton<TInput, TOutput> ?? throw new InvalidOperationException("Invalid skeleton type");
return await skel.ExecuteAsync(input, context);
}
public async Task ExecuteOnEnvironmentAsync<TEnvironment, TInput, TOutput>(TEnvironment env, string configText, IExecutionAdapter<TEnvironment> adapter, SkeletonContext context = null)
{
context ??= new();
var output = await ExecuteAsync<TInput, TOutput>(configText, adapter.ExtractInput<TInput>(env, context), context);
await adapter.ApplyOutputAsync(env, output, context);
}
}
#endregion
#region Composite Skeletons
// OPTIMIZED: SequentialSkeleton 同步执行使用表达式树编译的强类型委托
public class SequentialSkeleton<TInput, TOutput> : SkeletonBase<TInput, TOutput>, ICompositeSkeleton
{
private IReadOnlyList<ISkeleton> _children;
private bool _set;
private Func<TInput, SkeletonContext, TOutput> _compiledSync; // 缓存编译后的委托
private IReadOnlyList<Func<object, SkeletonContext, Task<object>>> _asyncDelegates; // 异步委托缓存
public SequentialSkeleton(string name = null, string desc = null) : base(name ?? "Sequential", desc ?? "Sequential composition") { }
public IEnumerable<ISkeleton> GetChildren() => _children ?? Enumerable.Empty<ISkeleton>();
public void SetChildren(IEnumerable<ISkeleton> children)
{
if (_set) throw new InvalidOperationException("Children already set");
var list = (children ?? throw new ArgumentNullException(nameof(children))).ToList();
var expected = typeof(TInput);
for (int i = 0; i < list.Count; i++)
{
var child = list[i];
var iface = child.GetType().GetInterfaces().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ISkeleton<,>) && t.GetGenericArguments()[0].IsAssignableFrom(expected))
?? throw new InvalidOperationException($"Child at index {i} input type not compatible with {expected}.");
expected = iface.GetGenericArguments()[1];
}
if (!typeof(TOutput).IsAssignableFrom(expected))
throw new InvalidOperationException($"Last child output type {expected} not compatible with {typeof(TOutput)}.");
_children = list.AsReadOnly();
_set = true;
// 编译同步执行链
_compiledSync = CompileSyncChain();
// 编译异步执行链委托
_asyncDelegates = CompileAsyncDelegates();
}
private Func<TInput, SkeletonContext, TOutput> CompileSyncChain()
{
if (_children == null || _children.Count == 0)
return (input, ctx) => throw new InvalidOperationException("No children to execute.");
var inputParam = Expression.Parameter(typeof(TInput), "input");
var ctxParam = Expression.Parameter(typeof(SkeletonContext), "ctx");
Expression current = inputParam;
Type currentType = typeof(TInput);
foreach (var child in _children)
{
var childType = child.GetType();
var skeletonInterface = childType.GetInterfaces()
.First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISkeleton<,>));
var inputType = skeletonInterface.GetGenericArguments()[0];
var outputType = skeletonInterface.GetGenericArguments()[1];
if (!inputType.IsAssignableFrom(currentType))
throw new InvalidOperationException($"Type mismatch: cannot convert from {currentType} to {inputType}.");
var executeMethod = skeletonInterface.GetMethod("Execute");
if (executeMethod == null)
throw new InvalidOperationException("Execute method not found.");
var convertedCurrent = Expression.Convert(current, inputType);
var call = Expression.Call(Expression.Constant(child), executeMethod, convertedCurrent, ctxParam);
current = Expression.Convert(call, typeof(object));
currentType = outputType;
}
if (!typeof(TOutput).IsAssignableFrom(currentType))
throw new InvalidOperationException($"Final output type {currentType} cannot be assigned to {typeof(TOutput)}.");
var finalConvert = Expression.Convert(current, typeof(TOutput));
var lambda = Expression.Lambda<Func<TInput, SkeletonContext, TOutput>>(finalConvert, inputParam, ctxParam);
return lambda.Compile();
}
private IReadOnlyList<Func<object, SkeletonContext, Task<object>>> CompileAsyncDelegates()
{
var delegates = new List<Func<object, SkeletonContext, Task<object>>>();
foreach (var child in _children)
{
var iface = child.GetType().GetInterfaces().First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISkeleton<,>));
var inputType = iface.GetGenericArguments()[0];
var outputType = iface.GetGenericArguments()[1];
var method = typeof(SequentialSkeleton<TInput, TOutput>).GetMethod(nameof(CreateAsyncWrapper), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
.MakeGenericMethod(inputType, outputType);
var wrapper = (Func<object, SkeletonContext, Task<object>>)method.Invoke(null, new object[] { child });
delegates.Add(wrapper);
}
return delegates.AsReadOnly();
}
private static Func<object, SkeletonContext, Task<object>> CreateAsyncWrapper<TIn, TOut>(ISkeleton<TIn, TOut> skeleton)
{
return async (obj, ctx) => await skeleton.ExecuteAsync((TIn)obj, ctx).ConfigureAwait(false);
}
protected override TOutput ExecuteCore(TInput input, SkeletonContext ctx)
{
if (_compiledSync == null) throw new InvalidOperationException("Children not set.");
return _compiledSync(input, ctx);
}
protected override async Task<TOutput> ExecuteCoreAsync(TInput input, SkeletonContext ctx)
{
if (_asyncDelegates == null) throw new InvalidOperationException("Children not set.");
object current = input;
foreach (var del in _asyncDelegates)
{
current = await del(current, ctx).ConfigureAwait(false);
}
return (TOutput)current;
}
}
public class ParallelForEachSkeleton<TInput, TOutput> : SkeletonBase<IEnumerable<TInput>, IEnumerable<TOutput>>, ICompositeSkeleton
{
private readonly int _dop;
private ISkeleton<TInput, TOutput> _element;
private bool _set;
public ParallelForEachSkeleton(int dop = –1, string name = null, string desc = null) : base(name ?? "ParallelForEach", desc ?? "Parallel for-each") => _dop = dop;
public IEnumerable<ISkeleton> GetChildren() => _element != null ? new[] { _element } : Enumerable.Empty<ISkeleton>();
public void SetChildren(IEnumerable<ISkeleton> children)
{
if (_set) throw new InvalidOperationException("Children already set");
var list = children?.ToList() ?? throw new ArgumentNullException(nameof(children));
if (list.Count != 1 || list[0] is not ISkeleton<TInput, TOutput> typed)
throw new InvalidOperationException("Need exactly one child of correct type");
_element = typed;
_set = true;
}
protected override IEnumerable<TOutput> ExecuteCore(IEnumerable<TInput> input, SkeletonContext ctx)
{
var list = input.ToList();
var results = new TOutput[list.Count];
Parallel.For(0, list.Count, new() { CancellationToken = ctx.CancellationToken, MaxDegreeOfParallelism = _dop },
i => results[i] = _element.Execute(list[i], ctx));
return results;
}
protected override async Task<IEnumerable<TOutput>> ExecuteCoreAsync(IEnumerable<TInput> input, SkeletonContext ctx)
{
var list = input.ToList();
var results = new TOutput[list.Count];
using var sem = new SemaphoreSlim(_dop > 0 ? _dop : int.MaxValue);
await Task.WhenAll(list.Select(async (item, i) =>
{
await sem.WaitAsync(ctx.CancellationToken).ConfigureAwait(false);
try { ctx.CancellationToken.ThrowIfCancellationRequested(); results[i] = await _element.ExecuteAsync(item, ctx); }
finally { sem.Release(); }
})).ConfigureAwait(false);
return results;
}
}
#endregion
}


