Snet Framework -- 数据模型参考
最后更新: 2026-07-21
命名空间: Snet.Model.data, Snet.Model.@event
源代码: Snet.Model/data/, Snet.Model/event/
架构概览
Snet Framework 定义了 21 个数据模型类,分为四个逻辑区域:事件基础设施、结果模型层次结构、地址系统和配置模型。所有结果模型提供 GetDetails(out ...) 解构器和 CreateSuccessResult/CreateFailureResult 静态工厂方法。每个模型覆盖 ToString() 通过 System.Text.Json 生成 JSON。
EventArgsAsync (CancellationToken)
├── BaseModel (Status, Message, Time)
│ ├── EventInfoResult
│ └── ResultModel (+ResultData)
│ ├── EventDataResult
│ └── OperateResult (+RunTime)
└── EventLanguageResult (Status, Message, Language, Time)
地址系统(独立层次)
├── Address ────────────────── AddressDetails 列表容器
├── AddressDetails ──────────── 单个地址定义
├── AddressValue : AddressDetails ── 带数据的地址
├── AddressValueSimplify [Proto] ── 轻量级传输模型
└── AddressValueSimplifyProtobuf [Proto] ── 批量容器
配置模型
├── WriteModel, WAModel ──────── 设备写入与 WebApi 配置
├── AddressMq, AddressParse ──── MQ 管道与解析管道
├── ResponseModel ────────────── MQ 响应反序列化
├── BytesModel ───────────────── 字节数组映射
├── LanguageModel ────────────── 多语言资源配置
├── PluginModel ──────────────── 插件元数据
└── ParamModel ───────────────── 基于反射的 UI 生成
异常
└── CustomException : Exception ── 自动捕获调用位置
第一部分:事件基础设施
1.1 EventArgsAsync
文件: event/EventArgsAsync.cs
继承层次: 独立的基类
为异步事件处理程序提供数据。持有 CancellationToken(JSON 忽略)和静态 Empty 单例。
| 成员 | 类型 | 描述 |
|---|---|---|
CancellationToken |
CancellationToken(只读) |
传播取消通知(JSON 忽略) |
Empty |
static readonly EventArgsAsync |
无数据事件的单例 |
// 带取消感知的静态工厂
var args = EventArgsAsync.CreateOrDefault(token);
// 检查 token 是否可取消
if (args.CancellationToken.CanBeCanceled)
{
// 处理取消
}
1.2 EventHandlerAsync<TEvent>
文件: event/EventHandlerAsync.cs
类型: 委托
public delegate Task EventHandlerAsync<in TEvent>(
object? sender, TEvent e) where TEvent : EventArgsAsync;
异步委托,约束为 EventArgsAsync 派生的事件参数。
1.3 EventingWrapperAsync<TEvent>
文件: event/EventingWrapperAsync.cs
类型: struct(线程安全)
线程安全的异步事件包装器,管理订阅、取消订阅和触发。缓存调用列表以提升性能。
| 方法 | 描述 |
|---|---|
AddHandler(handler) |
订阅处理程序 |
RemoveHandler(handler) |
取消订阅处理程序 |
InvokeAsync(sender, parameter) |
触发所有处理程序(非 async,避免 struct 拷贝) |
Takeover(other) |
从另一个包装器转移处理程序 |
关键行为:
- 静默捕获
OperationCanceledException - 将所有其他异常路由到
_onException回调 - 首次调用后缓存
GetInvocationList()结果以优化后续调用
var wrapper = new EventingWrapperAsync<EventDataResult>(
"MyContext", OnExceptionHandler);
// 订阅
wrapper.AddHandler(async (sender, e) =>
{
await ProcessAsync(e);
});
// 触发
await wrapper.InvokeAsync(this, eventData);
第二部分:结果模型层次结构
2.1 BaseModel
文件: data/BaseModel.cs
继承: EventArgsAsync
结果模型层次结构的根节点。包含状态、消息和时间戳。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
Status |
bool |
false |
操作成功指示符 |
Message |
string? |
null |
描述/错误消息 |
Time |
DateTime |
DateTime.Now |
响应时间戳 |
| 方法 | 签名 | 描述 |
|---|---|---|
GetDetails |
(out string? message) -> bool |
解构获取消息 |
GetDetails |
(out BaseModel result) -> bool |
解构获取自身 |
ToString |
() -> string |
JSON 序列化 |
var result = new BaseModel(true, "操作完成");
// 解构模式
if (result.GetDetails(out string? message))
{
Console.WriteLine($"成功: {message}");
}
else
{
Console.WriteLine($"失败: {message}");
}
2.2 EventInfoResult
文件: data/EventInfoResult.cs
继承: BaseModel
用于通过 IEvent.OnInfoEvent 传递通信状态事件。仅包含状态和消息(无数据负载)。
| 静态工厂方法 | 签名 |
|---|---|
CreateSuccessResult |
(string successMessage) -> EventInfoResult |
CreateFailureResult |
(string failureMessage) -> EventInfoResult |
// 发布连接状态
var info = EventInfoResult.CreateSuccessResult("PLC 已连接");
info.GetDetails(out EventInfoResult result);
2.3 ResultModel
文件: data/ResultModel.cs
继承: BaseModel
扩展 BaseModel,增加通用结果数据负载。提供 8 个 GetDetails 重载用于灵活的数据解构。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
ResultData |
object? |
null |
结果数据负载 |
| 关键方法 | 签名 |
|---|---|
GetSource<T> |
() -> T? |
GetDetails |
(out object? resultData) -> bool |
GetDetails<T> |
(out T? resultData) -> bool |
GetDetails |
(out object? resultData, out string? message) -> bool |
GetDetails<T> |
(out T? resultData, out string? message) -> bool |
GetDetails |
(out string? message, out object? resultData) -> bool |
GetDetails<T> |
(out string? message, out T? resultData) -> bool |
var result = new ResultModel(true, "完成", new { Value = 42 });
// 类型化提取
if (result.GetDetails<dynamic>(out var data, out string? msg))
{
Console.WriteLine(data.Value); // 42
}
// 泛型源提取
var source = result.GetSource<MyDto>();
2.4 EventDataResult
文件: data/EventDataResult.cs
继承: ResultModel
用于通过 IEvent.OnDataEvent 传递数据采集事件。包装状态、消息和数据结果。
| 静态工厂方法 | 签名 |
|---|---|
CreateSuccessResult |
(string successMessage) -> EventDataResult |
CreateSuccessResult<T> |
(string successMessage, T resultData) -> EventDataResult |
CreateFailureResult |
(string failureMessage) -> EventDataResult |
// 发布采集数据
var data = EventDataResult.CreateSuccessResult(
"读取完成", new float[] { 1.0f, 2.5f, 3.7f });
data.GetDetails(out EventDataResult result);
2.5 OperateResult
文件: data/OperateResult.cs
继承: ResultModel
统一的操作返回模型。增加了毫秒级执行时间追踪。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
RunTime |
int |
0 | 执行时间(毫秒) |
| 静态工厂方法 | 签名 | 默认 RunTime |
|---|---|---|
CreateSuccessResult |
(string msg, int? runTime) -> OperateResult |
8ms |
CreateSuccessResult<T> |
(string msg, T data, int? runTime) -> OperateResult |
8ms |
CreateFailureResult |
(string msg, int? runTime) -> OperateResult |
8ms |
var sw = Stopwatch.StartNew();
// ... 执行操作 ...
sw.Stop();
var result = OperateResult.CreateSuccessResult(
"批量写入完成", affectedRows, (int)sw.ElapsedMilliseconds);
Console.WriteLine($"状态: {result.Status}, 耗时: {result.RunTime}ms");
2.6 EventLanguageResult
文件: data/EventLanguageResult.cs
继承: EventArgsAsync(不是 BaseModel -- 平行分支)
用于通过 IEvent.OnLanguageEvent 传递语言变更通知。拥有独立的 Status、Message、Language 和 Time 属性,不继承 BaseModel。
| 属性 | 类型 | 描述 |
|---|---|---|
Status |
bool |
操作成功 |
Message |
string? |
描述 |
Language |
LanguageType? |
当前语言 |
Time |
DateTime(默认: DateTime.Now) |
时间戳 |
| 方法 | 签名 |
|---|---|
GetDetails |
(out string? message) -> bool |
GetDetails |
(out string? message, out LanguageType? language) -> bool |
GetDetails |
(out LanguageType? language) -> bool |
GetDetails |
(out EventLanguageResult result) -> bool |
| 静态工厂方法 | 签名 |
|---|---|
CreateSuccessResult |
(string msg) -> EventLanguageResult |
CreateSuccessResult |
(string msg, LanguageType? lang) -> EventLanguageResult |
CreateFailureResult |
(string msg) -> EventLanguageResult |
CreateFailureResult |
(string msg, LanguageType? lang) -> EventLanguageResult |
// 通知语言切换
var langResult = EventLanguageResult.CreateSuccessResult(
"已切换为英文", LanguageType.en);
langResult.GetDetails(out string? msg, out LanguageType? lang);
第三部分:地址系统
3.1 Address
文件: data/Address.cs
继承层次: 独立类
AddressDetails 集合的容器。通过 SN 标识(可用于存储机台号、组名、车间、工厂)。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
SN |
string |
Guid.NewGuid().ToUpperNString() |
唯一标识符 |
AddressArray |
List<AddressDetails> |
null |
地址集合 |
CreationTime |
DateTime |
DateTime.Now |
创建时间戳 |
| 方法 | 签名 | 描述 |
|---|---|---|
GetAddressInfo |
(string? name, string? anotherName) -> AddressDetails? |
按名称/别名查找(两者均为 null 返回 null) |
CheckAddress |
() -> bool |
验证所有地址均已设置 AddressName |
RemoveVirtualAddress |
() -> List<AddressDetails> |
筛选实际地址(返回新列表) |
GetRealityAddressCount |
() -> int |
统计实际地址数量(无中间分配) |
var address = new Address("Machine-01", detailsList, DateTime.Now);
// 查找特定地址
var detail = address.GetAddressInfo(AddressName: "D100");
// 过滤虚拟地址用于订阅
var realOnly = address.RemoveVirtualAddress();
// 使用前验证
if (address.CheckAddress())
{
// 所有地址有效
}
3.2 AddressDetails
文件: data/AddressDetails.cs
继承层次: 独立类
定义单个地址点,包含完整元数据:名称、类型、编码、长度、MQ 参数和解析参数。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
SN |
string |
Guid.NewGuid().ToUpperNString() |
唯一标识符 |
AddressName |
string |
null |
PLC 地址名称(唯一) |
Length |
ushort |
1 |
数组类型时的数组长度 |
EncodingType |
EncodingType |
ANSI |
字符串编码 |
AddressAnotherName |
string? |
null |
地址别名 |
AddressPropertyName |
string? |
null |
实体属性名称映射 |
AddressDescribe |
string? |
null |
可读描述 |
AddressExtendParam |
object? |
null |
扩展通信参数 |
IsEnable |
bool |
true |
地址是否启用 |
AddressMqParam |
AddressMq? |
null |
MQ 发布配置 |
AddressParseParam |
AddressParse? |
null |
数据解析配置 |
AddressDataType |
DataType |
String |
读写数据类型 |
AddressType |
AddressType |
Reality |
实际地址或虚拟地址 |
// 最小定义
var detail = new AddressDetails("D100", DataType.Float);
// 完整定义
var detail = new AddressDetails(
sn: "Machine-01",
addressName: "D200",
addressAnotherName: "TempSensor",
addressPropertyName: "Temperature",
addressDescribe: "炉温",
addressExtendParam: null,
addressMqParam: new AddressMq("snet/data", "{{temp:{0}}}", null),
addressParseParam: null,
addressDataType: DataType.Short,
addressType: AddressType.Reality,
isEnable: true,
length: 1,
encodingType: EncodingType.ANSI
);
3.3 AddressValue
文件: data/AddressValue.cs
继承: AddressDetails
扩展了采集数据的 AddressDetails。包含质量、结果值、原始值、消息和时间戳。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
Quality |
QualityType |
None (-1) |
数据质量状态 |
Message |
string |
null |
详细信息 |
ResultValue |
object? |
null |
解析后的值(解析后) |
OriginalValue |
object? |
null |
原始值(解析前) |
Time |
DateTime |
DateTime.Now |
采集/更新时间 |
| 方法 | 签名 | 描述 |
|---|---|---|
GetDetails |
(out object? resultValue, out object? originalValue, out string? message, out DateTime? time) -> QualityType |
完整解构 |
GetSimplify |
() -> AddressValueSimplify |
转换为轻量级传输模型 |
SET |
(AddressDetails details) -> AddressValue |
从父类复制属性 |
SET |
(object? value, byte[] source, DateTime time, BytesModel param, string? msg, QualityType quality) -> AddressValue |
从字节解析结果设置 |
var av = new AddressValue(QualityType.Normal, 42.5f, new byte[] { 0x42, 0x2A }, "正常");
// 解构
var quality = av.GetDetails(
out object? resultValue,
out object? originalValue,
out string? message,
out DateTime? time);
// 转换为轻量级模型
AddressValueSimplify simple = av.GetSimplify();
3.4 AddressValueSimplify
文件: data/AddressValueSimplify.cs
标记: [ProtoContract](Protobuf-net)
轻量级传输模型,使用短属性名以实现紧凑的网络序列化。包含 9 个 ProtoMember 字段。
| 属性 | 类型 | ProtoMember | 默认值 | 描述 |
|---|---|---|---|---|
SN |
string? |
1 | null |
唯一标识符 |
Add |
string? |
2 | null |
点位地址 |
L |
ushort |
3 | 1 |
数据长度 |
ENC |
EncodingType |
4 | ANSI |
编码类型 |
Q |
QualityType |
5 | None |
数据质量 |
Msg |
string? |
6 | null |
详细信息 |
VAL |
string? |
7 | null |
采集数值(JSON 字符串) |
Ts |
long |
8 | DateTime.UtcNow.Ticks |
采集时间戳(ticks) |
DT |
DataType |
9 | String |
数据类型 |
// 从 AddressValue 构造
var simple = addressValue.GetSimplify();
// 手动构造
var simple = new AddressValueSimplify(
sn: "M01", add: "D100", l: 1, enc: EncodingType.ANSI,
q: QualityType.Normal, val: "42.5", msg: "正常",
ts: DateTime.UtcNow.Ticks, dt: DataType.Float
);
3.5 AddressValueSimplifyProtobuf
文件: data/AddressValueSimplifyProtobuf.cs
标记: [ProtoContract]
用于批量 Protobuf 序列化的容器,包含多个 AddressValueSimplify 实例。
| 属性 | 类型 | ProtoMember | 默认值 | 描述 |
|---|---|---|---|---|
Ts |
long |
1 | DateTime.UtcNow.Ticks |
批次时间戳 |
ITM |
IEnumerable<AddressValueSimplify> |
2 | [] |
数据项集合 |
var batch = new AddressValueSimplifyProtobuf(simplifyList);
// 通过 protobuf-net 序列化
using var stream = new MemoryStream();
Serializer.Serialize(stream, batch);
byte[] data = stream.ToArray();
第四部分:配置模型
4.1 WriteModel
文件: data/WriteModel.cs
定义向设备地址写入数据的参数。配合 IWrite 接口使用。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
Value |
object |
null |
要写入的值(不能为空) |
AddressDataType |
DataType |
String |
目标数据类型 |
EncodingType |
EncodingType |
ANSI |
字符串编码 |
var write = new WriteModel(42.5f, DataType.Float, EncodingType.ANSI);
await writer.WriteAsync("D100", write);
4.2 WAModel
文件: data/WAModel.cs
配置数据采集引擎内置的 WebApi 服务器。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
IpAddress |
string |
"127.0.0.1" |
监听 IP 地址 |
Port |
int |
6688 |
监听端口 |
CrossDomain |
bool |
false |
启用 CORS |
var wa = new WAModel("0.0.0.0", 6688, crossDomain: true);
4.3 AddressMq
文件: data/AddressMq.cs
配置采集数据通过消息队列发布。每个地址可选择性地发布到 MQ。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
Topic |
string |
null |
MQ 主题 |
ContentFormat |
string? |
null |
格式字符串({0} = 数据占位符) |
ISns |
List<string>? |
null |
MQ 实例 SN(null = 所有打开的实例) |
var mqParam = new AddressMq(
topic: "snet/device/data",
contentFormat: "{{\"address\":\"{0}\",\"value\":\"{0}\"}}",
iSns: null // 发布到所有可用的 MQ 实例
);
4.4 AddressParse
文件: data/AddressParse.cs
通过反射对采集数据进行后处理的配置。每个地址最多有一个解析配置。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
ReflectionParam |
object[]? |
null |
[0] = 基础数据, [1] = 方法 SN |
反射机制调用 <方法>(string 地址名, string 地址值) -> string 来转换原始值。
var parse = new AddressParse(new object[] { baseData, "MethodGUID" });
4.5 ResponseModel
文件: data/ResponseModel.cs
当配置 ResponseType.ContentWithTopic 时使用。从包含主题和内容的 MQ 响应中反序列化。
| 属性 | 类型 | 描述 |
|---|---|---|
Topic |
string |
消息主题 |
Content |
object |
消息内容 |
var response = JsonSerializer.Deserialize<ResponseModel>(mqMessage);
ProcessMessage(response.Topic, response.Content);
4.6 BytesModel
文件: data/BytesModel.cs
将字节数组映射到结构化数据。定义地址、偏移量、长度、数据类型、编码和字节序。同时支持 System.Text.Json 和 Newtonsoft.Json 枚举字符串序列化。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
Address |
string |
null |
地址名称 |
Describe |
string |
null |
描述 |
StartBit |
int |
0 |
起始位偏移 |
Length |
ushort |
1 |
数据长度 |
BoolIndex |
int |
0 |
布尔位索引(每字节 0-7) |
DataType |
DataType |
Bool |
数据类型 |
EncodingType |
EncodingType |
UTF8 |
字符串编码 |
DataFormat |
DataFormat |
DCBA |
字节序 |
var bm = new BytesModel(
address: "DB100.DBD0",
describe: "电机转速",
startBit: 0,
length: 2, // 2 字节 = 16 位
dataType: DataType.Short,
encodingType: EncodingType.UTF8,
dataFormat: DataFormat.DCBA,
boolIndex: -1 // -1 表示非布尔位
);
4.7 LanguageModel
文件: data/LanguageModel.cs
配置多语言资源加载。指向解决方案、资源字典和程序集文件。
| 属性 | 类型 | 描述 |
|---|---|---|
Source |
string |
存放资源字典的解决方案名称 |
Dictionary |
string |
资源文件名称 |
AssemblyFile |
string |
包含程序集清单的文件名称或路径 |
var lang = new LanguageModel(
source: "Snet.Resources",
dictionary: "Strings.zh-CN.xaml",
assemblyFile: "Snet.Resources.dll"
);
4.8 PluginModel
文件: data/PluginModel.cs
插件加载器使用的插件元数据。描述名称、命名空间、配置模板、路径、类型和版本。
| 属性 | 类型 | 描述 |
|---|---|---|
Name |
string |
插件名称 |
Namespace |
string |
插件命名空间 |
ConfigFormat |
string |
配置文件格式模板 |
Path |
string |
插件所在目录路径 |
Type |
PluginType |
插件类别(Daq=0, Mq=1) |
Version |
string |
插件版本 |
var plugin = new PluginModel(
name: "SiemensS7",
@namespace: "Snet.Plugin.Daq.Siemens",
configFormat: "{\"ip\":\"{ip}\",\"rack\":{rack},\"slot\":{slot}}",
path: "./plugins/SiemensS7/",
version: "2.1.0",
type: PluginType.Daq
);
4.9 ParamModel
文件: data/ParamModel.cs
基于反射的 UI 生成模型。允许插件声明式地定义其配置界面。包含嵌套类型层次结构,用于定义表单字段及其约束。
嵌套类型
ParamModel
├── dataCate (枚举) ─────────── 前端控件类型
│ ├── text (0) ─────────────── 文本框
│ ├── select (1) ───────────── 下拉框
│ ├── radio (2) ────────────── 单选框
│ ├── unmber (3) ───────────── 整数框
│ └── upload (4) ───────────── 上传
├── subset
│ ├── Name ─────────────────── 分组名称
│ ├── Description ──────────── 分组描述
│ └── Propertie: List<propertie>
├── propertie
│ ├── DataCate ─────────────── 控件类型
│ ├── Default ──────────────── 默认值
│ ├── Initial ──────────────── 初始值
│ ├── Description ──────────── 字段描述
│ ├── PropertyName ─────────── 绑定的属性名称
│ ├── Show ─────────────────── 可见性(true/false)
│ ├── Use ──────────────────── 可编辑(false = 只读)
│ ├── MustFillIn ───────────── 必填项
│ ├── DetailsTips ──────────── 提示文本
│ ├── Pattern ──────────────── 正则校验
│ ├── FailTips ─────────────── 校验失败提示
│ └── Options: List<options>
├── options
│ ├── Key ──────────────────── 选项键
│ └── Value ────────────────── 选项值
└── (精简版变体)
├── subsetSimplify ────────── 精简版分组(Name, Description, Propertie)
└── propertieSimplify ─────── 精简版属性(Description, PropertyName)
| ParamModel 属性 | 类型 | 描述 |
|---|---|---|
Code |
string(只读) |
Name 的别名 |
Name |
string |
参数模型名称 |
Description |
string |
描述 |
Subset |
List<subset> |
参数分组 |
var param = new ParamModel
{
Name = "SiemensConfig",
Description = "西门子 PLC 连接参数",
Subset = new List<ParamModel.subset>
{
new()
{
Name = "Connection",
Description = "网络设置",
Propertie = new List<ParamModel.propertie>
{
new()
{
DataCate = ParamModel.dataCate.text,
PropertyName = "IpAddress",
Description = "PLC IP 地址",
Default = "192.168.0.1",
Show = true,
Use = true,
MustFillIn = true,
Pattern = @"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$",
FailTips = "IP 地址格式无效"
},
new()
{
DataCate = ParamModel.dataCate.select,
PropertyName = "Protocol",
Description = "通信协议",
Default = "S7",
Show = true,
Use = true,
Options = new List<ParamModel.options>
{
new() { Key = "S7", Value = "S7" },
new() { Key = "S7-200", Value = "S7-200" }
}
}
}
}
}
};
第五部分:异常模型
5.1 CustomException
文件: data/CustomException.cs
继承: Exception
使用 [CallerFilePath]、[CallerMemberName] 和 [CallerLineNumber] 自动捕获调用者的文件路径、方法名和行号。提供同步和异步工厂方法。
| 静态工厂方法 | 签名 | 描述 |
|---|---|---|
Create |
(string message) -> CustomException |
同步创建 |
Create |
(string message, Exception inner) -> CustomException |
同步创建(含内部异常) |
CreateAsync |
(string message, CancellationToken token) -> Task<CustomException> |
异步创建 |
CreateAsync |
(string message, Exception inner, CancellationToken token) -> Task<CustomException> |
异步创建(含内部异常) |
消息格式: file {文件名} the {方法名} method {行号} line exception: {消息}
// 同步抛出
throw CustomException.Create("无效的寄存器地址");
// 异步创建
var ex = await CustomException.CreateAsync("超时", cts.Token);
// 输出: "file DeviceReader.cs the ReadAsync method 42 line exception: 超时"
跨领域设计模式
ToString() / JSON 序列化
每个模型覆盖 ToString(),通过 Snet.Utility 扩展方法 ToJson(true) 返回 JSON:
var result = OperateResult.CreateSuccessResult("完成", data, 15);
string json = result.ToString();
// {"Status":true,"Message":"完成","ResultData":{...},"Time":"...","RunTime":15}
GetDetails() 解构模式
所有结果类型遵循一致的解构模式,支持 C# out 变量:
// 模式: 方法返回 bool(状态),属性通过 out 参数输出
if (result.GetDetails(out var data, out string? message))
{
// 成功路径
}
else
{
// 失败路径 -- message 包含错误信息
}
工厂方法模式
所有结果类型提供 CreateSuccessResult 和 CreateFailureResult 静态工厂方法,支持单表达式构造,无需使用 new:
// 替代: new OperateResult(false, "错误", 0)
return OperateResult.CreateFailureResult("错误");
