在现代软件架构中,数据处理流程往往需要兼顾清晰性、可组合性与可扩展性。传统的命令式编程容易导致代码耦合度高、难以复用,而纯粹的数学抽象(如集合论、范畴论)则为数据变换提供了坚实的形式化基础。SkeletonFlow 是一个轻量级的数据流处理框架,它将每个处理步骤视为一个态射(morphism),通过组合子(combinators)将简单态射构建为复杂流程,其设计思想深受函数式编程和组合子逻辑的启发。本文将从数学和逻辑学的角度剖析SkeletonFlow的核心概念,展示如何用代数结构描述数据处理管道。
一、核心设计:态射与上下文
在范畴论中,一个范畴由对象(objects)和态射(morphisms)组成,态射表示对象之间的结构保持映射。SkeletonFlow中的每个处理单元------骨架(Skeleton)------恰好对应一个态射:它将输入类型 TInput 的对象映射到输出类型 TOutput 的对象。这一映射不仅可以是纯函数,还可以携带副作用和上下文信息,从而更贴近实际工程需求。
1.1 态射的签名与自描述
每个骨架都满足以下签名:
类型变换:TInput → TOutput,在范畴论中可视为对象间的箭头。
自描述:每个骨架拥有名称和描述,这类似于在逻辑系统中为每个推理规则赋予标签,便于追踪和调试。
同步与异步双重性:骨架同时支持同步和异步求值,对应数学中函数的即时计算与惰性求值两种视角。
形式上,一个骨架可看作一个带有上下文参数 C 的映射:
f: (TInput, C) → TOutput
其中 C 是 SkeletonContext,它封装了取消信号、日志记录器、依赖注入容器以及一个键值对存储。上下文的存在使得态射之间可以共享信息,类似于逻辑推导中的假设集或环境。
1.2 上下文作为全局参数
从逻辑学角度看,SkeletonContext 类似于一个可能世界或环境模型,其中包含了推导过程中所需的额外信息。例如,取消令牌 CancellationToken 对应于逻辑中的终止条件;Items 字典则允许在不同推理步骤间传递临时结论,类似自然演绎中的临时假设。
二、组合子:从简单态射构造复杂流程
范畴论的核心在于态射的组合。SkeletonFlow提供了一系列组合子(combinators),它们以高阶函数的形式将多个态射结合为新的态射,同时保持类型正确性。这些组合子构成了一个用于描述数据流的领域特定语言(DSL)。
2.1 顺序组合(Then)
给定两个态射 f: A → B 和 g: B → C,顺序组合产生新的态射 h: A → C,定义为 h(a) = g(f(a))。这对应于数学中函数的复合运算。在SkeletonFlow中,Then 操作不仅保证了类型匹配,还通过上下文传播确保了取消令牌的一致传递。
2.2 分支组合(Branch)
分支组合对应逻辑中的条件判断。给定一个谓词态射 p: A → Boolean 和两个分支态射 f, g: A → B,分支组合产生态射:
text
h(a) = if p(a) then f(a) else g(a)
这可以看作集合论中的分情况定义,或逻辑中的条件证明。谓词本身也可以是一个骨架,从而实现复杂条件的动态计算。
2.3 循环组合(Loop)
循环组合源于递归函数理论。给定一个谓词 p: A → Boolean 和一个循环体 f: A → A,循环组合产生态射:
text
h(a) = while p(a) do a := f(a) 最终返回 a
这一构造对应于原始递归的一种形式,但更接近于编程语言中的 while 循环。从范畴论角度,它是通过不动点算子定义的,要求类型 A 上存在一个终止条件。
2.4 并行组合(Parallel)
并行组合将两个输入类型相同的态射 f: A → B 和 g: A → C 组合为一个输出为乘积对象 (B, C) 的态射:
text
h(a) = (f(a), g(a))
这在范畴论中对应配对(pairing)操作。SkeletonFlow的并行组合在同步执行时利用多线程并发计算,异步执行时则通过任务并行,体现了计算理论中的并行复合。
2.5 元素处理提升(ForEach / SelectMany)
当处理集合类型时,SkeletonFlow提供了将态射提升到集合范畴上的操作。给定一个态射 f: T → U,ForEach 将其提升为 List → List++,对应数学中的 map 函数。而 SelectMany 则对应单子(monad)中的 bind 操作:若 f: T → List++,则 SelectMany 产生 List → List++,并自动展平结果。++++++
三、流式API:范畴论语法的自然表达
为了让态射的组合更符合直觉,SkeletonFlow在应用层提供了 Flow<TInput, TOutput> 类型,它本质上是一个包装后的态射。该API的设计借鉴了函数式编程中的管道(pipeline)模式,每个方法调用都对应于一个组合子应用。
3.1 起始态射与恒等态射
Flow.Start() 创建了一个恒等态射 id: T → T,它在范畴论中是每个对象必备的单位元。恒等态射在组合中充当中性元素,例如 id ∘ f = f 且 f ∘ id = f。
3.2 链式组合与类型推导
通过 Then 方法,用户可以像串联函数一样连接多个态射,编译器会自动推导中间类型。这种链式调用对应于数学中的复合运算,但更接近于一种点自由(point-free)风格------用户无需显式命名中间变量。
3.3 专用扩展方法
针对输出类型为集合的流,SkeletonFlow提供了一系列扩展方法,如 Filter、Map、Reduce 等。这些方法实际上是内置态射的语法糖,它们分别对应集合论中的子集构造、像集构造和折叠操作。例如,Filter(predicate) 对应于从原集合中选出满足谓词 predicate 的元素构成子集。
四、动态配置:将流程表示为数据
从逻辑学角度看,一个数据处理流程可以视为一个证明树或推导序列。SkeletonFlow允许将这样的流程序列化为JSON格式,从而实现流程定义的动态加载。这相当于将操作语义(operational semantics)编码为数据,然后由引擎解释执行。
4.1 流程的代数表示
一个动态配置实质上是一个抽象语法树(AST),其中每个节点对应一个态射,节点参数对应态射的参数,子节点对应组合子所需的子态射。例如,一个顺序组合的配置可表示为:
text
Sequential(Filter(predicate), Map(selector))
这类似于逻辑中的合取(conjunction)但顺序敏感。
4.2 注册表与类型解析
为了将配置字符串转换为可执行的态射,SkeletonFlow引入了注册表(ISkeletonRegistry),它本质上是一个从类型名称到态射构造函数的映射。这一机制类似于范畴论中的索引范畴(indexed category),其中每个名称索引一个态射。
4.3 引擎与适配器
引擎(SkeletonEngine)负责解释配置并执行流程。它还支持适配器(IExecutionAdapter),使得流程可以嵌入到不同的环境(如HTTP请求、消息队列)中。这类似于将范畴中的态射扩展到带有环境的索引范畴。
五、自定义态射:扩展范畴
SkeletonFlow鼓励用户定义自己的态射,只需继承 SkeletonBase<TInput, TOutput> 并实现核心方法。从数学角度,这相当于在原有范畴中添加新的箭头,只要保证它们满足组合律和单位律(即 Execute 和 ExecuteAsync 的行为一致)。
自定义态射可以封装任意业务逻辑,但它们仍然保持与内置态射相同的接口,从而能够无缝地参与组合。这体现了开放封闭原则在范畴论层面的应用:范畴对于新态射是开放的,但已存在的组合子无需修改。
六、实例:订单处理的逻辑形式
让我们用逻辑符号描述一个订单处理流程:
初始数据:订单集合 O = {o₁, o₂, ..., oₙ},每个订单有属性 cancelled 和 total。
目标:计算所有未取消订单的总金额。
该流程可表示为以下态射组合:
text
H = Filter(¬cancelled) ∘ Map(total) ∘ Reduce(+, 0)
其中:
Filter(¬cancelled) 是谓词 p(o) = ¬o.cancelled 对应的子集构造。
Map(total) 是函数 f(o) = o.total 的像集构造。
Reduce(+, 0) 是集合上的折叠操作,对应加法幺半群 (ℝ, +, 0)。
若需要并行处理,可将 Map 替换为并行版本,其数学本质是将 f 提升到并行的乘积范畴中。
七、总结
SkeletonFlow不仅是一个实用的编程框架,更是一个将范畴论、组合子逻辑和类型理论应用于工程实践的范例。它通过以下方式体现了数学思想:
态射抽象:每个处理步骤都是类型化的变换,可组合、可复用。
组合子完备性:顺序、分支、循环、并行等组合子覆盖了常见控制流,且满足代数性质。
上下文传递:将环境参数显式化,避免隐式全局状态,符合逻辑中的假设管理。
流程即数据:支持动态配置,将操作语义编码为AST,便于分析和优化。
无论是构建ETL管道、业务规则引擎还是实时分析系统,SkeletonFlow都能以简洁而严谨的方式描述数据处理逻辑。它让开发者能够站在数学的高度思考问题,同时不牺牲工程上的灵活性与性能。
通过本文的分析,我们希望读者能够看到数学抽象在软件设计中的价值,并在实践中探索更多范畴论与逻辑学的应用可能。
csharp
namespace SkeletonFlow
{
#nullable disable
#region Core
public interface ISkeleton { }
/// <summary>
/// 基于 ECS 上下文重构后的 SkeletonContext,内部使用 IContext 实现协作,
/// 同时保持所有原有公共属性行为完全不变。
/// </summary>
public class SkeletonContext : IDisposable
{
private readonly IContext _context;
private CancellationTokenSource _linkedCts;
private readonly ConcurrentDictionary<string, object> _items = new ConcurrentDictionary<string, object>();
private IServiceProvider _serviceProvider;
private ILogger _logger;
private bool _disposed;
public SkeletonContext(IContext context = null)
{
_context = context ?? new ConcreteContext();
_linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_context.CancellationToken);
}
public IServiceProvider ServiceProvider
{
get => _serviceProvider;
set => _serviceProvider = value;
}
public CancellationToken CancellationToken
{
get => _linkedCts.Token;
set
{
if (value != _linkedCts.Token)
{
_linkedCts.Dispose();
_linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_context.CancellationToken, value);
}
}
}
public ILogger Logger
{
get => _logger;
set => _logger = value;
}
public IDictionary<string, object> Items => _items;
public void Dispose()
{
if (_disposed) return;
_linkedCts.Dispose();
(_context as IDisposable)?.Dispose();
_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)
{
context ??= new SkeletonContext();
context.CancellationToken.ThrowIfCancellationRequested();
return ExecuteCore(input, context);
}
public async Task<TOutput> ExecuteAsync(TInput input, SkeletonContext context = null)
{
context ??= new SkeletonContext();
context.CancellationToken.ThrowIfCancellationRequested();
return await ExecuteCoreAsync(input, context).ConfigureAwait(false);
}
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>
{
readonly Func<TInput, SkeletonContext, TOutput> _sync;
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 is null ? null : (i, _) => sync(i),
async is 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)
{
if (_sync != null)
return _sync(input, ctx);
if (_async != null)
throw new NotSupportedException("同步执行需要提供同步委托,当前只有异步委托。请提供同步委托或使用 ExecuteAsync。");
throw new NotSupportedException("没有提供任何执行委托。");
}
protected override async Task<TOutput> ExecuteCoreAsync(TInput input, SkeletonContext ctx)
{
if (_async != null)
return await _async(input, ctx).ConfigureAwait(false);
return await Task.Run(() => ExecuteCore(input, ctx), ctx.CancellationToken).ConfigureAwait(false);
}
}
#endregion
#region BuiltIn
public static class BuiltInSkeletons
{
public static ISkeleton<IEnumerable<T>, IEnumerable<T>> Filter<T>(string name, Func<T, bool> predicate, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, IEnumerable<T>>(name,
syncWithCtx: (src, ctx) =>
{
var list = new List<T>();
foreach (var item in src)
{
ctx.CancellationToken.ThrowIfCancellationRequested();
if (predicate(item)) list.Add(item);
}
return list;
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
var list = new List<T>();
foreach (var item in src)
{
ctx.CancellationToken.ThrowIfCancellationRequested();
if (predicate(item)) list.Add(item);
}
return list;
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
public static ISkeleton<IEnumerable<T>, IEnumerable<TResult>> Map<T, TResult>(string name, Func<T, TResult> selector, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, IEnumerable<TResult>>(name,
syncWithCtx: (src, ctx) =>
{
var list = new List<TResult>();
foreach (var item in src)
{
ctx.CancellationToken.ThrowIfCancellationRequested();
list.Add(selector(item));
}
return list;
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
var list = new List<TResult>();
foreach (var item in src)
{
ctx.CancellationToken.ThrowIfCancellationRequested();
list.Add(selector(item));
}
return list;
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
public static ISkeleton<IEnumerable<T>, TAccumulate> Reduce<T, TAccumulate>(string name, TAccumulate seed, Func<TAccumulate, T, TAccumulate> acc, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, TAccumulate>(name,
syncWithCtx: (src, ctx) =>
{
var r = seed;
foreach (var i in src)
{
ctx.CancellationToken.ThrowIfCancellationRequested();
r = acc(r, i);
}
return r;
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
var r = seed;
foreach (var i in src)
{
ctx.CancellationToken.ThrowIfCancellationRequested();
r = acc(r, i);
}
return r;
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
public static ISkeleton<IEnumerable<T>, IEnumerable<T>> Skip<T>(string name, int count, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, IEnumerable<T>>(name,
syncWithCtx: (src, ctx) =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.Skip(count).ToList();
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.Skip(count).ToList();
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
public static ISkeleton<IEnumerable<T>, IEnumerable<T>> Take<T>(string name, int count, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, IEnumerable<T>>(name,
syncWithCtx: (src, ctx) =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.Take(count).ToList();
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.Take(count).ToList();
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
public static ISkeleton<IEnumerable<T>, IEnumerable<T>> Distinct<T>(string name, IEqualityComparer<T> comparer = null, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, IEnumerable<T>>(name,
syncWithCtx: (src, ctx) =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.Distinct(comparer).ToList();
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.Distinct(comparer).ToList();
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
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) =>
new DelegateSkeleton<IEnumerable<T>, IEnumerable<IGrouping<TKey, T>>>(name,
syncWithCtx: (src, ctx) =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.GroupBy(keySelector, comparer).ToList();
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.GroupBy(keySelector, comparer).ToList();
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
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) =>
new DelegateSkeleton<IEnumerable<T>, IOrderedEnumerable<T>>(name,
syncWithCtx: (src, ctx) =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return descending ? src.OrderByDescending(keySelector, comparer) : src.OrderBy(keySelector, comparer);
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return descending ? src.OrderByDescending(keySelector, comparer) : src.OrderBy(keySelector, comparer);
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
public static ISkeleton<IEnumerable<T>, T> First<T>(string name, Func<T, bool> predicate = null, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, T>(name,
syncWithCtx: (src, ctx) =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return predicate == null ? src.First() : src.First(predicate);
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return predicate == null ? src.First() : src.First(predicate);
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
public static ISkeleton<IEnumerable<T>, T> Single<T>(string name, Func<T, bool> predicate = null, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, T>(name,
syncWithCtx: (src, ctx) =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return predicate == null ? src.Single() : src.Single(predicate);
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return predicate == null ? src.Single() : src.Single(predicate);
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
public static ISkeleton<IEnumerable<T>, List<T>> ToList<T>(string name, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, List<T>>(name,
syncWithCtx: (src, ctx) =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.ToList();
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.ToList();
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
public static ISkeleton<IEnumerable<T>, T[]> ToArray<T>(string name, string desc = null) =>
new DelegateSkeleton<IEnumerable<T>, T[]>(name,
syncWithCtx: (src, ctx) =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.ToArray();
},
asyncWithCtx: async (src, ctx) =>
{
return await Task.Run(() =>
{
ctx.CancellationToken.ThrowIfCancellationRequested();
return src.ToArray();
}, ctx.CancellationToken).ConfigureAwait(false);
},
desc);
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 ParallelOptions { 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);
var tasks = list.Select(async (item, i) =>
{
await sem.WaitAsync(ctx.CancellationToken);
try
{
ctx.CancellationToken.ThrowIfCancellationRequested();
results[i] = transform(item);
}
finally { sem.Release(); }
});
await Task.WhenAll(tasks).ConfigureAwait(false);
return results;
},
desc);
}
#endregion
#region Composition
public static class SkeletonExtensions
{
private static SkeletonContext EnsureContext(SkeletonContext context) => context ?? new SkeletonContext();
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) =>
{
ctx = EnsureContext(ctx);
return next.Execute(first.Execute(i, ctx), ctx);
},
async (i, ctx) =>
{
ctx = EnsureContext(ctx);
return 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)) :
trueBranch == null ? throw new ArgumentNullException(nameof(trueBranch)) :
falseBranch == null ? throw new ArgumentNullException(nameof(falseBranch)) :
new DelegateSkeleton<TInput, TOutput>("Branch",
(i, ctx) =>
{
ctx = EnsureContext(ctx);
return cond(i) ? trueBranch.Execute(i, ctx) : falseBranch.Execute(i, ctx);
},
async (i, ctx) =>
{
ctx = EnsureContext(ctx);
return cond(i) ? await trueBranch.ExecuteAsync(i, ctx) : await falseBranch.ExecuteAsync(i, ctx);
});
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) =>
{
ctx = EnsureContext(ctx);
return condSkel.Execute(i, ctx) ? trueBranch.Execute(i, ctx) : falseBranch.Execute(i, ctx);
},
async (i, ctx) =>
{
ctx = EnsureContext(ctx);
return 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)) :
body == null ? throw new ArgumentNullException(nameof(body)) :
new DelegateSkeleton<TInput, TInput>("Loop",
(i, ctx) =>
{
ctx = EnsureContext(ctx);
var cur = i;
while (cond(cur)) cur = body.Execute(cur, ctx);
return cur;
},
async (i, ctx) =>
{
ctx = EnsureContext(ctx);
var cur = i;
while (cond(cur)) cur = await body.ExecuteAsync(cur, ctx).ConfigureAwait(false);
return cur;
});
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) =>
{
ctx = EnsureContext(ctx);
var cur = i;
while (condSkel.Execute(cur, ctx)) cur = body.Execute(cur, ctx);
return cur;
},
async (i, ctx) =>
{
ctx = EnsureContext(ctx);
var cur = i;
while (await condSkel.ExecuteAsync(cur, ctx).ConfigureAwait(false)) cur = await body.ExecuteAsync(cur, ctx).ConfigureAwait(false);
return cur;
});
public static ISkeleton<TInput, (TOutput1, TOutput2)> Parallel<TInput, TOutput1, TOutput2>(
this ISkeleton<TInput, TOutput1> s1,
ISkeleton<TInput, TOutput2> s2) =>
s1 == null ? throw new ArgumentNullException(nameof(s1)) :
s2 == null ? throw new ArgumentNullException(nameof(s2)) :
new DelegateSkeleton<TInput, (TOutput1, TOutput2)>("Parallel",
(i, ctx) =>
{
ctx = EnsureContext(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);
},
async (i, ctx) =>
{
ctx = EnsureContext(ctx);
var t1 = s1.ExecuteAsync(i, ctx);
var t2 = s2.ExecuteAsync(i, ctx);
await System.Threading.Tasks.Task.WhenAll(t1, t2).ConfigureAwait(false);
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) =>
{
ctx = EnsureContext(ctx);
return src.Select(i => element.Execute(i, ctx)).ToList();
},
async (src, ctx) =>
{
ctx = EnsureContext(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) =>
{
ctx = EnsureContext(ctx);
return src.SelectMany(i => element.Execute(i, ctx)).ToList();
},
async (src, ctx) =>
{
ctx = EnsureContext(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);
}
public interface IOperationProvider
{
TDelegate GetOperation<TDelegate>(string name, params object[] parameters) where TDelegate : Delegate;
}
public class DictionaryOperationProvider : IOperationProvider
{
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);
}
public class DefaultSkeletonRegistry : ISkeletonRegistry
{
static readonly ConcurrentDictionary<string, Type> _typeCache = new();
static readonly ConcurrentDictionary<string, byte> _negativeTypeCache = new();
readonly ConcurrentDictionary<string, Func<object[], ISkeleton>> _factories = new();
public System.Text.Json.JsonSerializerOptions JsonOptions { get; set; } = new() { PropertyNameCaseInsensitive = true };
public void Register<TSkeleton>() where TSkeleton : class, ISkeleton
{
Register(typeof(TSkeleton).Name, args => CreateInstance<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))
{
object[] args = ResolveParams(config.Parameters, null, services);
var skeleton = factory(args);
if (skeleton is ICompositeSkeleton comp && config.Children?.Count > 0)
comp.SetChildren(config.Children.Select(c => Resolve(c, services)));
return skeleton;
}
Type targetType = ResolveType(config.Type);
if (targetType == null)
throw new InvalidOperationException($"Skeleton type '{config.Type}' not found and no factory registered.");
object[] ctorArgs = ResolveParams(config.Parameters, targetType, services);
var instance = Activator.CreateInstance(targetType, ctorArgs);
if (instance is not ISkeleton skeletonInstance)
throw new InvalidOperationException($"Type '{config.Type}' does not implement ISkeleton.");
if (skeletonInstance is ICompositeSkeleton comp2 && config.Children?.Count > 0)
comp2.SetChildren(config.Children.Select(c => Resolve(c, services)));
return skeletonInstance;
}
private TSkeleton CreateInstance<TSkeleton>(object[] args) where TSkeleton : class, ISkeleton
{
return (TSkeleton)Activator.CreateInstance(typeof(TSkeleton), args);
}
object[] ResolveParams(IDictionary<string, object> parameters, Type typeHint, IServiceProvider services)
{
if (parameters == null || parameters.Count == 0) return Array.Empty<object>();
if (typeHint != null)
{
var constructors = typeHint.GetConstructors();
if (constructors.Length == 0)
throw new InvalidOperationException($"No public constructor found for type {typeHint}");
ConstructorInfo bestCtor = null;
int bestMatchCount = -1;
foreach (var ctor in constructors)
{
var paramInfos = ctor.GetParameters();
int matchCount = paramInfos.Count(p => parameters.ContainsKey(p.Name));
if (matchCount > bestMatchCount)
{
bestMatchCount = matchCount;
bestCtor = ctor;
}
}
if (bestCtor == null)
throw new InvalidOperationException($"No suitable constructor found for type {typeHint}");
var paramInfosBest = bestCtor.GetParameters();
var args = new object[paramInfosBest.Length];
for (int i = 0; i < paramInfosBest.Length; i++)
{
var param = paramInfosBest[i];
if (parameters.TryGetValue(param.Name, out var val) && val != null)
args[i] = ResolveValue(val, param.ParameterType, services);
else if (param.HasDefaultValue)
args[i] = param.DefaultValue;
else
throw new InvalidOperationException($"Missing parameter '{param.Name}' for type {typeHint.Name}");
}
return args;
}
else
{
var keys = parameters.Keys.OrderBy(k => k).ToList();
var args = new object[keys.Count];
for (int i = 0; i < keys.Count; i++)
{
args[i] = ResolveValue(parameters[keys[i]], null, services);
}
return args;
}
}
object ResolveValue(object val, Type target, IServiceProvider services) => val switch
{
string s when s.StartsWith("$service:") => ResolveServiceReference(s[9..], services),
System.Text.Json.JsonElement je => ConvertJsonElement(je, target),
_ => val
};
private object ConvertJsonElement(System.Text.Json.JsonElement je, Type target)
{
if (target != null)
{
return System.Text.Json.JsonSerializer.Deserialize(je, target, JsonOptions);
}
else
{
return 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()
};
}
}
private object ResolveServiceReference(string typeName, IServiceProvider services)
{
if (services == null)
throw new InvalidOperationException("Service provider is not available to resolve service reference.");
Type serviceType = ResolveType(typeName);
if (serviceType == null)
throw new InvalidOperationException($"Service type '{typeName}' not found.");
return services.GetService(serviceType)
?? throw new InvalidOperationException($"Service '{typeName}' not registered in provider.");
}
Type ResolveType(string name)
{
if (_typeCache.TryGetValue(name, out var type))
return type;
if (_negativeTypeCache.ContainsKey(name))
return null;
type = Type.GetType(name);
if (type == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
type = asm.GetTypes().FirstOrDefault(t => t.FullName == name || t.Name == name);
if (type != null) break;
}
catch (ReflectionTypeLoadException)
{
continue;
}
}
}
if (type != null)
_typeCache[name] = type;
else
_negativeTypeCache[name] = 1;
return type;
}
}
public interface IConfigParser
{
SkeletonConfig Parse(string configText);
}
public class JsonConfigParser : IConfigParser
{
readonly System.Text.Json.JsonSerializerOptions _options;
public JsonConfigParser(System.Text.Json.JsonSerializerOptions options = null) => _options = options ?? new System.Text.Json.JsonSerializerOptions { 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
{
readonly ISkeletonRegistry _registry;
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 SkeletonContext();
var skeleton = _registry.Resolve(config, context?.ServiceProvider) as ISkeleton<TInput, TOutput> ?? throw new InvalidOperationException("Invalid skeleton type");
return await skeleton.ExecuteAsync(input, context);
}
public async Task ExecuteOnEnvironmentAsync<TEnvironment, TInput, TOutput>(TEnvironment env, string configText, IExecutionAdapter<TEnvironment> adapter, SkeletonContext context = null)
{
context ??= new SkeletonContext();
var output = await ExecuteAsync<TInput, TOutput>(configText, adapter.ExtractInput<TInput>(env, context), context);
await adapter.ApplyOutputAsync(env, output, context);
}
}
#endregion
#region Composite Skeletons (Runtime)
public class SequentialSkeleton<TInput, TOutput> : SkeletonBase<TInput, TOutput>, ICompositeSkeleton
{
IReadOnlyList<ISkeleton> _children;
bool _set;
public SequentialSkeleton(string name = null, string desc = null) : base(name ?? "Sequential", desc ?? "Sequential composition") { }
public void SetChildren(IEnumerable<ISkeleton> children)
{
if (_set) throw new InvalidOperationException("Children already set");
var list = (children ?? throw new ArgumentNullException(nameof(children))).ToList();
Type currentInputType = typeof(TInput);
Type expectedOutputType = typeof(TOutput);
ISkeleton prev = null;
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<,>));
if (iface == null)
throw new InvalidOperationException($"Child at index {i} does not implement ISkeleton<TIn,TOut>.");
var genericArgs = iface.GetGenericArguments();
Type childInput = genericArgs[0];
Type childOutput = genericArgs[1];
if (i == 0)
{
if (!childInput.IsAssignableFrom(currentInputType))
throw new InvalidOperationException($"First child input type {childInput} is not compatible with sequential input {currentInputType}.");
}
else
{
var prevOutput = prev.GetType().GetInterfaces()
.First(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ISkeleton<,>))
.GetGenericArguments()[1];
if (!childInput.IsAssignableFrom(prevOutput))
throw new InvalidOperationException($"Child at index {i} input type {childInput} is not compatible with previous output {prevOutput}.");
}
if (i == list.Count - 1)
{
if (!expectedOutputType.IsAssignableFrom(childOutput))
throw new InvalidOperationException($"Last child output type {childOutput} is not compatible with sequential output {expectedOutputType}.");
}
prev = child;
}
_children = list.AsReadOnly();
_set = true;
}
protected override TOutput ExecuteCore(TInput input, SkeletonContext ctx)
{
dynamic cur = input;
foreach (var child in _children)
cur = ((dynamic)child).Execute(cur, ctx);
return (TOutput)cur;
}
protected override async Task<TOutput> ExecuteCoreAsync(TInput input, SkeletonContext ctx)
{
dynamic cur = input;
foreach (var child in _children)
cur = await ((dynamic)child).ExecuteAsync(cur, ctx).ConfigureAwait(false);
return (TOutput)cur;
}
}
public class ParallelForEachSkeleton<TInput, TOutput> : SkeletonBase<IEnumerable<TInput>, IEnumerable<TOutput>>, ICompositeSkeleton
{
readonly int _dop;
ISkeleton<TInput, TOutput> _element;
bool _set;
public ParallelForEachSkeleton(int dop = -1, string name = null, string desc = null) : base(name ?? "ParallelForEach", desc ?? "Parallel for-each") => _dop = dop;
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 ParallelOptions { 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);
try
{
ctx.CancellationToken.ThrowIfCancellationRequested();
results[i] = await _element.ExecuteAsync(item, ctx);
}
finally { sem.Release(); }
})).ConfigureAwait(false);
return results;
}
}
#endregion
}
namespace SkeletonFlow.App
{
#region 核心流式构建器
/// <summary>
/// 表示一个可执行的数据流,封装了 <see cref="ISkeleton{TInput, TOutput}"/>。
/// 提供链式组合方法与执行入口。
/// </summary>
/// <typeparam name="TInput">流的输入类型</typeparam>
/// <typeparam name="TOutput">流的输出类型</typeparam>
public class Flow<TInput, TOutput>
{
private readonly ISkeleton<TInput, TOutput> _skeleton;
internal Flow(ISkeleton<TInput, TOutput> skeleton)
{
_skeleton = skeleton ?? throw new ArgumentNullException(nameof(skeleton));
}
/// <summary>
/// 获取底层的 <see cref="ISkeleton{TInput, TOutput}"/>。
/// </summary>
public ISkeleton<TInput, TOutput> Skeleton => _skeleton;
/// <summary>
/// 将当前流与下一个骨架顺序组合。
/// </summary>
public Flow<TInput, TNewOutput> Then<TNewOutput>(ISkeleton<TOutput, TNewOutput> next)
=> new Flow<TInput, TNewOutput>(_skeleton.Then(next));
/// <summary>
/// 同步执行流。
/// </summary>
public TOutput Execute(TInput input, SkeletonContext context = null)
=> _skeleton.Execute(input, context);
/// <summary>
/// 异步执行流。
/// </summary>
public Task<TOutput> ExecuteAsync(TInput input, SkeletonContext context = null)
=> _skeleton.ExecuteAsync(input, context);
}
/// <summary>
/// 提供创建 <see cref="Flow{TInput, TOutput}"/> 的入口点。
/// </summary>
public static class Flow
{
private static readonly Lazy<DefaultSkeletonRegistry> _defaultRegistry = new(() =>
{
var reg = new DefaultSkeletonRegistry();
return reg;
});
/// <summary>
/// 获取或设置默认的骨架注册表,用于 <see cref="Load{TInput, TOutput}(string, IServiceProvider)"/> 方法。
/// </summary>
public static ISkeletonRegistry DefaultRegistry { get; set; } = _defaultRegistry.Value;
/// <summary>
/// 创建一个起始流,输入输出类型相同(恒等变换)。
/// </summary>
public static Flow<T, T> Start<T>() => new Flow<T, T>(new IdentitySkeleton<T>());
/// <summary>
/// 从现有的 <see cref="ISkeleton{TInput, TOutput}"/> 创建流。
/// </summary>
public static Flow<TInput, TOutput> FromSkeleton<TInput, TOutput>(ISkeleton<TInput, TOutput> skeleton)
=> new Flow<TInput, TOutput>(skeleton);
/// <summary>
/// 从 JSON 配置动态创建流(需要预先在 <see cref="DefaultRegistry"/> 中注册骨架类型,或类型具有公共构造函数且参数可匹配)。
/// </summary>
public static Flow<TInput, TOutput> Load<TInput, TOutput>(string configText, IServiceProvider services = null)
{
var registry = DefaultRegistry;
var parser = new JsonConfigParser();
var config = parser.Parse(configText);
var skeleton = registry.Resolve(config, services);
if (skeleton is ISkeleton<TInput, TOutput> typed)
return new Flow<TInput, TOutput>(typed);
throw new InvalidOperationException($"解析出的骨架类型与期望的 {typeof(TInput).Name}->{typeof(TOutput).Name} 不匹配。");
}
/// <summary>
/// 并行执行多个输入相同的骨架,输出为值元组。
/// </summary>
public static Flow<TInput, (TOutput1, TOutput2)> Parallel<TInput, TOutput1, TOutput2>(
Flow<TInput, TOutput1> flow1,
Flow<TInput, TOutput2> flow2)
=> new Flow<TInput, (TOutput1, TOutput2)>(flow1.Skeleton.Parallel(flow2.Skeleton));
}
/// <summary>
/// 恒等骨架,用于 <see cref="Flow.Start{T}"/>。
/// </summary>
internal class IdentitySkeleton<T> : SkeletonBase<T, T>
{
public IdentitySkeleton() : base("Identity", "Pass through input") { }
protected override T ExecuteCore(T input, SkeletonContext context) => input;
protected override Task<T> ExecuteCoreAsync(T input, SkeletonContext context) => Task.FromResult(input);
}
#endregion
#region 针对集合的扩展方法
/// <summary>
/// 提供针对 <see cref="IEnumerable{T}"/> 输出流的便捷操作。
/// </summary>
public static class FlowEnumerableExtensions
{
public static Flow<TInput, IEnumerable<TElement>> Filter<TInput, TElement>(
this Flow<TInput, IEnumerable<TElement>> flow,
Func<TElement, bool> predicate,
string name = null)
=> flow.Then(BuiltInSkeletons.Filter(name ?? "Filter", predicate));
public static Flow<TInput, IEnumerable<TResult>> Map<TInput, TElement, TResult>(
this Flow<TInput, IEnumerable<TElement>> flow,
Func<TElement, TResult> selector,
string name = null)
=> flow.Then(BuiltInSkeletons.Map(name ?? "Map", selector));
public static Flow<TInput, TAccumulate> Reduce<TInput, TElement, TAccumulate>(
this Flow<TInput, IEnumerable<TElement>> flow,
TAccumulate seed,
Func<TAccumulate, TElement, TAccumulate> func,
string name = null)
=> flow.Then(BuiltInSkeletons.Reduce(name ?? "Reduce", seed, func));
public static Flow<TInput, IEnumerable<TElement>> Skip<TInput, TElement>(
this Flow<TInput, IEnumerable<TElement>> flow,
int count,
string name = null)
=> flow.Then(BuiltInSkeletons.Skip<TElement>(name ?? "Skip", count));
public static Flow<TInput, IEnumerable<TElement>> Take<TInput, TElement>(
this Flow<TInput, IEnumerable<TElement>> flow,
int count,
string name = null)
=> flow.Then(BuiltInSkeletons.Take<TElement>(name ?? "Take", count));
public static Flow<TInput, IEnumerable<TElement>> Distinct<TInput, TElement>(
this Flow<TInput, IEnumerable<TElement>> flow,
IEqualityComparer<TElement> comparer = null,
string name = null)
=> flow.Then(BuiltInSkeletons.Distinct(name ?? "Distinct", comparer));
public static Flow<TInput, IEnumerable<IGrouping<TKey, TElement>>> GroupBy<TInput, TElement, TKey>(
this Flow<TInput, IEnumerable<TElement>> flow,
Func<TElement, TKey> keySelector,
IEqualityComparer<TKey> comparer = null,
string name = null)
=> flow.Then(BuiltInSkeletons.GroupBy(name ?? "GroupBy", keySelector, comparer));
public static Flow<TInput, IOrderedEnumerable<TElement>> OrderBy<TInput, TElement, TKey>(
this Flow<TInput, IEnumerable<TElement>> flow,
Func<TElement, TKey> keySelector,
bool descending = false,
IComparer<TKey> comparer = null,
string name = null)
=> flow.Then(BuiltInSkeletons.OrderBy(name ?? "OrderBy", keySelector, descending, comparer));
public static Flow<TInput, TElement> First<TInput, TElement>(
this Flow<TInput, IEnumerable<TElement>> flow,
Func<TElement, bool> predicate = null,
string name = null)
=> flow.Then(BuiltInSkeletons.First<TElement>(name ?? "First", predicate));
public static Flow<TInput, TElement> Single<TInput, TElement>(
this Flow<TInput, IEnumerable<TElement>> flow,
Func<TElement, bool> predicate = null,
string name = null)
=> flow.Then(BuiltInSkeletons.Single<TElement>(name ?? "Single", predicate));
public static Flow<TInput, List<TElement>> ToList<TInput, TElement>(
this Flow<TInput, IEnumerable<TElement>> flow,
string name = null)
=> flow.Then(BuiltInSkeletons.ToList<TElement>(name ?? "ToList"));
public static Flow<TInput, TElement[]> ToArray<TInput, TElement>(
this Flow<TInput, IEnumerable<TElement>> flow,
string name = null)
=> flow.Then(BuiltInSkeletons.ToArray<TElement>(name ?? "ToArray"));
public static Flow<TInput, IEnumerable<TElement>> ParallelForEach<TInput, TElement>(
this Flow<TInput, IEnumerable<TElement>> flow,
Func<TElement, TElement> transform,
int dop = -1,
string name = null)
=> flow.Then(BuiltInSkeletons.ParallelForEach(name ?? "ParallelForEach", transform, dop));
}
#endregion
#region 控制流扩展(分支、循环、并行处理)
/// <summary>
/// 提供分支、循环、ForEach 等控制流操作。
/// </summary>
public static class FlowControlExtensions
{
/// <summary>
/// 根据条件分支到两个子流(条件由委托计算)。
/// </summary>
public static Flow<TInput, TOutput> Branch<TInput, TOutput>(
this Flow<TInput, TOutput> flow,
Func<TOutput, bool> condition,
Flow<TOutput, TOutput> trueBranch,
Flow<TOutput, TOutput> falseBranch)
{
var condSkel = new DelegateSkeleton<TOutput, bool>("BranchCondition",
(i, _) => condition(i),
(i, _) => Task.FromResult(condition(i)));
var trueSkel = trueBranch.Skeleton;
var falseSkel = falseBranch.Skeleton;
var branchSkel = condSkel.Branch(trueSkel, falseSkel);
return flow.Then<TOutput>(branchSkel);
}
/// <summary>
/// 根据条件循环执行主体(条件由委托计算)。
/// </summary>
public static Flow<TInput, TOutput> Loop<TInput, TOutput>(
this Flow<TInput, TOutput> flow,
Func<TOutput, bool> condition,
Flow<TOutput, TOutput> body)
{
var condSkel = new DelegateSkeleton<TOutput, bool>("LoopCondition",
(i, _) => condition(i),
(i, _) => Task.FromResult(condition(i)));
var bodySkel = body.Skeleton;
var loopSkel = condSkel.Loop(bodySkel);
return flow.Then<TOutput>(loopSkel);
}
/// <summary>
/// 将处理单个元素的流提升为处理元素集合的流(顺序处理)。
/// </summary>
public static Flow<IEnumerable<TInput>, IEnumerable<TOutput>> ForEach<TInput, TOutput>(
this Flow<TInput, TOutput> elementFlow)
=> new Flow<IEnumerable<TInput>, IEnumerable<TOutput>>(elementFlow.Skeleton.ForEach());
/// <summary>
/// 将处理单个元素的流提升为并行处理元素集合的流。
/// </summary>
public static Flow<IEnumerable<TInput>, IEnumerable<TOutput>> ParallelForEach<TInput, TOutput>(
this Flow<TInput, TOutput> elementFlow,
int degreeOfParallelism = -1)
{
var parallelSkel = new ParallelForEachSkeleton<TInput, TOutput>(degreeOfParallelism);
((ICompositeSkeleton)parallelSkel).SetChildren(new[] { elementFlow.Skeleton });
return new Flow<IEnumerable<TInput>, IEnumerable<TOutput>>(parallelSkel);
}
/// <summary>
/// 将处理单个元素并返回集合的流提升为处理元素集合并展平结果的流。
/// </summary>
public static Flow<IEnumerable<TInput>, IEnumerable<TOutput>> SelectMany<TInput, TOutput>(
this Flow<TInput, IEnumerable<TOutput>> elementFlow)
=> new Flow<IEnumerable<TInput>, IEnumerable<TOutput>>(elementFlow.Skeleton.SelectMany());
}
#endregion
#region 复合骨架构建辅助(用于并行组合等)
/// <summary>
/// 提供创建复合骨架的快捷方式。
/// </summary>
public static class CompositeFlow
{
/// <summary>
/// 创建一个顺序执行的复合流(内部已通过 <see cref="Flow{TInput, TOutput}.Then"/> 隐式支持,本方法用于显式构建)。
/// </summary>
public static Flow<TInput, TOutput> Sequential<TInput, TOutput>(
IEnumerable<ISkeleton> children,
string name = null,
string description = null)
{
var seq = new SequentialSkeleton<TInput, TOutput>(name, description);
seq.SetChildren(children);
return new Flow<TInput, TOutput>(seq);
}
}
#endregion
}