Hi.Ltd 专题第 3 期·中|模块:Utilities(字符串扩展)|NuGet:Hi.Ltd 2026.7.11.1437
目标框架:net462 / net481 / net6.0 / net8.0
场景:配方名校验、INI/路径键、PLC IP、报文 ASCII/Hex 文本、空串与空白串拦截。

第3期·上:Utilities 数值类型扩展(工控寄存器与限幅换算)


本期目标

把上位机配置层与报文层里「空串、非法 IP、非数字、Hex 文本、截取段、TitleCase 显示」统一到 Hi.Ltd.Utilities 字符串扩展,返回值多数为 Result<T>。

读完你应能:

  1. 用 IsNullOrEmpty / IsNullOrWhiteSpace / IsNotNullOrEmpty 做配置与 HMI 输入门禁
  2. 用 IsIpAddressValid / IsNumberValid / IsInt32 / IsPathValid / IsValidEmail 等做字段级校验
  3. 用 HexToBytes / ToHexString / GenHexString / GetBytes / GetASCCIIChars 做报文文本 ↔ 二进制
  4. 用 Split / SplitFirst / SplitLast / Substring / TrimChars / Replace / Remove* 解析配方名与键路径
  5. 分清 README 快速入门里的 ToBase64 简化名与真实 API ToBase64String / FromBase64String

适用范围

场景API 方向
配方名、工单号、批次号非空IsNullOrWhiteSpace / IsNotNullOrEmpty
PLC IP、MES URL 主机段IsIpAddressValid / IsMatch
INI 里速度/温度字符串转数IsInt32/IsDouble → ToInt32/ToDouble
抓包日志:字节 ↔ Hex 文本ToHexString / HexToBytes / GenHexString
设备名显示规范化ToTitleCase / TrimChars / ToSnakeCase
路径合法性(导出目录)IsPathValid / GetDirectories / GetFiles

不该用 / 慎用

场景说明
把 JSON/YAML/INI 完整业务模型反序列化当「字符串小工具」用完就走FromJson/FromYaml/FromIni 等在 Utilities 上存在,系统讲解见下篇;复杂契约建议放到 Interop 期
热路径每包 ToHexString 打全帧Hex 日志放抽样/失败分支
正则 IsMatch 写灾难回溯工控输入应白名单 + 长度上限
文化/多语言资源CultureScope / GetResourceString 见下篇

关键约定

约定DLL 实测
校验类 Is*多数 → Result<bool>(另有 *Unsafe → bool)
变换类 To* / Split* / Trim*多数 → Result<string> 或 Result<T>
NewGuid()→ string(非 Result)
GetCombinations→ IEnumerable<string>
TrimToNull / ToSnakeCase→ string(裸)
FromJson→ T(泛型,失败行为以实现为准;系统讲见下篇)
另有 Result<string> 入参重载与上期桥接

分组详解

1. 空串 / 空白 / 相等:门禁组

方法典型入参返回说明
IsNullOrEmptystring / 数组 / Guid / 字典…Result<bool>空或 null
IsNullOrWhiteSpacestring / Result<string>Result<bool>null/空/纯空白
IsNullOrWhiteSpaceEx字符串族见 DLL扩展变体
IsNotNullOrEmpty同族Result<bool>正向门禁
IsEqual / IsNotEqual字符串等Result<bool>相等比较
Contains / StartsWith / EndsWithstring 或 Result<string>Result<bool>片段判断
using Hi.Ltd;

string recipeName = hmi.RecipeText;
if (recipeName.IsNullOrWhiteSpace().Content)
    return Error.Empty(nameof(recipeName), "配方名不能为空");

string a = "LINE-A";
string b = "line-a";
// 是否大小写敏感以具体重载为准;不确定就先规范化再比
Result<bool> same = a.IsEqual(b);

工控:下载配方前、写 INI 前、连 PLC 前,三位门禁一起做。


2. 格式校验:IP / 数字 / 路径 / 邮件 / 日期 / 语言字符

方法返回工控用途
IsIpAddressValidResult<bool>PLC/相机 IP
IsNumberValidResult<bool>是否整数串
IsValidRealNumber / IsValidUNumberResult<bool>实数 / 无符号数
IsPathValidResult<bool>导出路径、配方目录
IsValidEmailResult<bool>报警推送邮箱
IsValidDateFormatResult<bool>批次日期串
IsGuidResult<bool>工单 GUID
IsEnglish / IsChineseTextResult<bool>语言策略
IsContainsChinese / IsContainsChineseCharacterResult<bool>是否含中文
IsContainsSpecialCharsResult<bool>特殊字符拦截(文件名)
IsMatch / IsNotMatchResult<bool>正则(慎用)

类型探测(字符串是否可解析为某 CLR 类型):

IsBoolean / IsByte / IsChar / IsDecimal / IsDateTime / IsDouble / IsSingle /
IsInt16 / IsInt32 / IsInt64 / IsSByte / IsUInt16 / IsUInt32 / IsUInt64

全部安全版 → Result<bool>。

using Hi.Ltd;

string ip = "192.168.1.10";
if (!ip.IsIpAddressValid().Content)
    return Error.Format(nameof(ip), "PLC IP 非法");

string speed = ini["Axis","Speed"]; // 示意
if (!speed.IsInt32().Content)
    return Error.Format("Speed", "速度必须是整数");
Result<int> rpm = speed.ToInt32();

3. 字符串 → 数值 / 字节 / 布尔 / 时间

方法返回(DLL)说明
ToInt16/ToInt32/ToInt64/ToUInt*/ToByte/ToSByteResult<T>配置转寄存器值
ToDouble/ToSingle/ToDecimalResult<T>工程量
ToBoolean/ToBooleansResult<bool>…"true"/开关
ToDateTimeResult<DateTime>时间串
ToIPAddressResult<IPAddress>连板用
GetBytes(this string) / GetBytes(string, Encoding)Result<byte[]>默认 UTF-8(README)
ToBytes / ToByteResult<byte[]> / Result<byte>近义族
GetAsciiBytesResult<byte[]>ASCII 报文
HexToBytesResult<byte[]>Hex 文本 → 字节
FromBase64StringResult<byte[]>Base64 → 字节

空串 → Error.Empty;格式错 → Error.Format(README 对 Hex/Base64/部分转换的描述)。

using System.Text;
using Hi.Ltd;

// 报文 ASCII
Result<byte[]> ascii = "RD D100".GetBytes(Encoding.ASCII);

// 抓包窗口粘贴的 Hex
Result<byte[]> frame = "01030FA00001".HexToBytes();
if (!frame.Successed) return frame; // Result 桥

// Base64 配方备份片段
Result<byte[]> blob = backupB64.FromBase64String();

README 快速入门写过 ToBase64()/FromBase64()——包内公开名为 ToBase64String / FromBase64String(DLL 已核对)。


4. 数值/字节 → 字符串:显示与日志

方法入参侧返回说明
ToHexString(this byte/short/int/…, bool)数值Result<string>单值 Hex
GenHexString(this byte[])字节数组Result<string>缓冲区 Hex 文本
ToBase64String(this byte[] …)字节Result<string>(部分重载见 DLL)Base64
FormatWithDecimal(this long, int)整数Result<string>固定小数显示
ToSaveString字符串等Result<string>可落盘友好串
ToTitleCase字符串Result<string>标题格式
ToSnakeCase字符串stringsnake_case
ToStringBuilder字符串Result<StringBuilder>可变拼接
GetString(this byte[] …)字节Result<string>解码文本
NewGuid()无 this 或静态扩展形态string新 GUID 字符串
using Hi.Ltd;

byte[] pdu = frame.Content;
Result<string> hexLog = pdu.GenHexString();
plc.Debug("TX " + hexLog.Content);

long raw = 12345; // 0.001 工程单位
Result<string> show = raw.FormatWithDecimal(3); // 显示层

5. 切割 / 替换 / 子串 / 查找

方法返回说明
SplitResult<string>按分隔规则拆(重载多,详见 XML)
SplitFirst / SplitLastResult<string>只取首/尾段
SubstringResult<string>安全子串
ReplaceResult<string>替换
RemoveLast / RemoveOneResult / Result<string>删末段/一处
TrimCharsResult<string>按给定字符修剪
TrimToNullstring空白→null 风格
IndexOf / LastIndexOfResult<int>查找
GetDigit / GetDigitsResult<int> / 数组从字符串抽数字字符
using Hi.Ltd;

// 配方键:Line1/Recipe/Speed
string key = "Line1/Recipe/Speed";
Result<string> head = key.SplitFirst("/");  // Line1
Result<string> tail = key.SplitLast("/");   // Speed

string file = "batch_001.csv";
Result<string> name = file.RemoveLast("."); // 视重载语义去掉末段

6. 路径 / 文件枚举 / 描述 / 组合

方法返回说明
GetDirectoriesResult<string[]>子目录
GetFiles / GetFileResult<string[]>文件列举
GetDescriptionResult<string>描述信息(含枚举描述见下篇交叉)
GetCombinationsIEnumerable<string>组合生成(测试用例/许可键空间慎用)
FindChineseCharactersResult<Dictionary<int,string>>找出中文位置
using Hi.Ltd;

string root = @"D:\Recipes";
if (!root.IsPathValid().Content)
    return Error.Format(nameof(root), "配方根路径非法");

Result<string[]> files = root.GetFiles();

7. 与 Result<string> 的桥(点到为止)

几乎所有字符串 API 都有 this Result<string> 重载:通讯函数已经返回 Result<string> 时,可直接:

Result<string> msg = ReadAsciiReply();
Result<bool> okIp = msg.IsIpAddressValid();
Result<string> trimmed = msg.TrimChars(' ', '\r', '\n');

深层 Then/Match/BranchTrace 见第2期,不在此展开。


完整工控示例:配方名 + INI 速度 + Hex 下发

using System.Text;
using Hi.Ltd;

public static class RecipeTextPipeline
{
    public static Result<byte[]> BuildWriteFrame(string recipeName, string speedText, string plcIp)
    {
        if (recipeName.IsNullOrWhiteSpace().Content)
            return Error.Empty(nameof(recipeName), "配方名空");

        if (recipeName.IsContainsSpecialChars().Content)
            return Error.Format(nameof(recipeName), "配方名含特殊字符");

        if (!plcIp.IsIpAddressValid().Content)
            return Error.Format(nameof(plcIp), "PLC IP 非法");

        if (!speedText.IsInt32().Content)
            return Error.Format(nameof(speedText), "速度不是整数");

        Result<int> speed = speedText.ToInt32();
        if (!speed.Successed) return Error.Result(speed);

        Result<int> limited = speed.Content.Clamp(0, 3000); // 上篇数值
        if (!limited.Successed) return Error.Result(limited);

        // 示意:ASCII 命令 + 速度字
        string cmd = $"WR SPEED {limited.Content}";
        Result<byte[]> body = cmd.GetBytes(Encoding.ASCII);
        return body;
    }

    public static Result LogTx(byte[] pdu)
    {
        Result<string> hex = pdu.GenHexString();
        if (!hex.Successed) return hex;
        // Logging:pdu.Debug 不存在;用上期 Logger 扩展
        return Result.Success;
    }
}

现场坑

  1. 空串与空白串:只判 IsNullOrEmpty 挡不住 " "——HMI 输入用 IsNullOrWhiteSpace。
  2. IsInt32 为 true 仍可能 ToInt32 失败? 以 DLL 行为为准;生产仍要看 Successed。
  3. Hex 串带空格/换行:先 TrimChars/Replace 再 HexToBytes。
  4. 编码:GetBytes() 默认 UTF-8;PLC ASCII 协议请显式 Encoding.ASCII。
  5. README ToBase64 简化名:代码里用 ToBase64String/FromBase64String。
  6. NewGuid 返回 string:不要当 Result 解包。
  7. 中文路径 / 中文配方:用 IsContainsChinese* 做策略,而不是一刀切拒绝(看产线规范)。

异常点与应对

现象可能原因应对
Error.Empty空串进 Hex/Base64/GetBytes门禁前置
Error.FormatHex 奇数长度、非十六进制字符清洗输入;UI 限制字符集
IsIpAddressValid 过宽/过严IPv6/端口混在串里IP 与端口拆开校验
路径 API 失败权限/盘符不存在与 IO 期 FileUtils 配合
正则超时IsMatch 灾难表达式改白名单

FAQ

Q: 字符串转 int 用 ToInt32 还是 int.Parse?
A: 工控边界推荐 ToInt32→Result,和全库失败模型一致。

Q: GenHexString 和 ToHexString 怎么选?
A: 单数值调试用 ToHexString;整帧缓冲用 GenHexString。

Q: 序列化 FromJson 为何本篇不展开?
A: 属于 Utilities「其他/序列化」能力;Interop 期还会从模块视角再讲。


下篇预告

第3期·下:Utilities 其他类型扩展(字节序/CRC/集合/时间/序列化)

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐