1. 编写第一个插件
Cordis 插件是挂载到 Context 上的行为单元。Context.Plugin(plugin) 会用插件 fork 出的子 Context 调用它;
插件通过该 Context 注册自己贡献的所有内容。
编写插件
在 tmp/cordis-tutorial/Tutorial 目录中(参见准备工作),
把 Program.cs 写成宿主代码加上一行挂载:
ctx.Plugin(Plugin.From(c => Console.WriteLine("hello from my first plugin")));
Plugin.From(src/Cordis/Plugin.cs)把委托包成 IPlugin——这是 dsh 函数插件的对应物,也是本教程最常用的形态。
运行
dotnet run
预期输出:
hello from my first plugin
当没有任何内容继续运行时,进程会自行退出。具体过程:
- 宿主创建根
Context并StartAsync()。 ctx.Plugin(...)把插件挂载为一个子插件(fork + apply + 纳入激活图)。- Cordis 在 fork 出的子 Context 上运行你的委托。
你的代码里没有框架启动代码:插件描述自己的贡献;到第 6 章, 组合关系会整体移到 YAML 文档里。
其他两种插件形态
IPlugin 接口只有 Inject 与 ApplyAsync 两个成员;Plugin 基类是同步插件的便捷形态:
// 1. 委托插件(刚才写的)
ctx.Plugin(Plugin.From(c => { }));
// 2. 类插件:继承 Plugin,等价于 dsh 的对象插件
public sealed class HelloPlugin : Plugin
{
protected override void Apply(Context context)
=> Console.WriteLine("hello from class plugin");
}
ctx.Plugin(new HelloPlugin());
// 3. CordisService 子类(第 3 章介绍)
Context.Plugin 返回 PluginHandle——对应 dsh 的 fiber,是已挂载插件实例的运行时句柄:
IsActive / IsDisposed 查询状态,Completion 等待终态,DisposeAsync() 显式卸载。
挂载动作本身注册为父 scope 的 effect:卸载父级会连带卸载插件。
尝试制造错误
让委托抛出异常:
var handle = ctx.Plugin(Plugin.From(_ => throw new InvalidOperationException("apply exploded")));
try
{
await handle.Completion;
}
catch (PluginApplyException ex)
{
Console.WriteLine($"plugin failed: {ex.InnerException?.Message}");
}
apply 失败会回收该插件的 scope 并使 PluginHandle.Completion 以 PluginApplyException 失败——
不允许静默吞掉(src/Cordis/PluginHandle.cs)。这一点比 dsh 更显式:TS 版 fiber 失败体现在状态机里,
C# 版可以直接 await 到异常。
下一章:生命周期与 effect:插件卸载时会发生什么。