快速看懂 ByteSize 源码:一个 long Bits 如何优雅驱动十进制与二进制双体系转换
【免费下载链接】ByteSizeByteSize is a utility class that makes byte size representation in code easier by removing ambiguity of the value being represented. ByteSize is to bytes what System.TimeSpan is to time.项目地址: https://gitcode.com/gh_mirrors/byt/ByteSize
ByteSize 是一款 C# 字节大小工具库,它让字节单位转换变得清晰无歧义——就像System.TimeSpan之于时间。它的核心设计只有一个思路:用一个long Bits字段存储真实值,其余所有单位(KB、MB、KiB、GiB……)全部按需换算而来。今天带你完整剖析这套十进制/二进制双体系转换的源码实现,几行关键代码就能看懂。
先搞清楚:为什么需要 KB 和 KiB 两套体系
很多新手第一次会困惑:同样是"1 KB",硬盘厂商说是 1000 字节,而 Windows 任务管理器里的内存又按 1024 算。这不是谁错了,而是两套标准并存:
| 体系 | 换算比例 | 缩写 | 典型场景 |
|---|---|---|---|
| 📊 十进制(SI 标准) | 1 KB =1000 B | KB、MB、GB、TB | 硬盘容量、云存储计费 |
| 🔢 二进制(IEC 标准) | 1 KiB =1024 B | KiB、MiB、GiB、TiB | 内存、内存分配器 |
ByteSize 从 v2.0 起同时支持这两套体系(见 CHANGELOG.md 中的破坏性变更记录),这也是理解它源码的起点。
单一数据源:Bits 就是"宇宙真理"
打开核心文件src/ByteSizeLib/ByteSize.cs,结构体ByteSize只有两个字段:
public long Bits { get; } // 唯一整数真值 public double Bytes { get; } // Bits / 8,只是缓存构造函数里的逻辑简单到极致(ByteSize.cs):
public ByteSize(long bits) { Bits = bits; Bytes = (double)bits / BitsInByte; // BitsInByte = 8 }为什么选long而不是double?因为位(bit)是不可再分的最小单位,整数存储零精度损失;而加减乘除的结果若出现小数字节,Bytes用double承接,兼顾精度与灵活性。所有比较、相等、排序操作最终都落在Bits上,比如CompareTo只有一行:this.Bits.CompareTo(other.Bits)。
十进制换算:5 个常量撑起整个体系
src/ByteSizeLib/DecimalByteSize.cs是一个partial 结构体,只干一件事——定义十进制换算:
- 5 个比例常量:
BytesInKiloByte = 1_000一直到BytesInPetaByte = 1_000_000_000_000_000 - 5 个符号常量:
KB、MB、GB、TB、PB - 5 个只读属性:
KiloBytes => Bytes / BytesInKiloByte - 5 个工厂方法:
FromKiloBytes、FromMegaBytes……全部收敛为new ByteSize(value * 常量)
也就是说,"1.5 MB" 并不神秘——它只是1500000 字节的另一种叫法,落到 Bits 就是 12000000。
二进制换算:同样 5 件套,只是底数换了
src/ByteSizeLib/BinaryByteSize.cs结构与十进制文件完全对称,只是常量换成了 1024 的幂:
| 常量 | 值 |
|---|---|
BytesInKibiByte | 1_024 |
BytesInMebiByte | 1_048_576 |
BytesInGibiByte | 1_073_741_824 |
BytesInTebiByte | 1_099_511_627_776 |
BytesInPebiByte | 1_125_899_906_842_624 |
两个文件合起来,ByteSize就拥有了bits、bytes + 十进制 5 级 + 二进制 5 级共 12 个视角,但它们读写的始终是同一个Bits。这正是"一个 long 驱动双体系"的全部秘密:没有转换,只有除法。
自动挑最大整数单位:ToString 的精髓
用户最在意的输出体验在ToString里(ByteSize.cs)。它配合LargestWholeNumberDecimalSymbol这类属性,从上往下逐级探测,找到"值 ≥ 1 的最大单位":
var size = ByteSize.FromKiloBytes(1000); size.ToString(); // "1 MB" ——十进制视角 size.ToBinaryString(); // "0.98 MiB" ——二进制视角细节也很讲究:
- 值为 0 时固定输出
"0 B",避免出现诡异的"0 b"; - 格式化串里写
KB、MiB等符号即可强制指定体系; - 数字格式走标准的
double.ToString,默认0.##保留两位小数,且支持区域性(de-DE文化下的1,55 kib也能正确解析)。
字符串解析:把 "1.55 MB" 读回来
Parse方法(ByteSize.cs)采用"数字与单位切半"策略:先找到第一个非数字字符,把前半段double.TryParse成数值,后半段按 switch 分派:
b/B大小写敏感——位不允许小数("1.5 b"会抛FormatException);KB、MB走十进制工厂方法;KiB、MiB走二进制工厂方法;- 解析失败可用
TryParse静默兜底,风格与 .NET 基类一致。
此外还有一个隐藏彩蛋:ByteSize标注了[TypeConverter(typeof(ByteSizeTypeConverter))](src/ByteSizeLib/ByteSizeTypeConverter.cs),意味着设计器、配置文件里可以直接写字符串,框架自动完成 Parse/ToString。
工程组织:测试如何保障双体系正确性
源码只有 6 个 .cs 文件,测试却按体系分得清清楚楚:
src/ByteSizeLib.Tests/Decimal/—— 十进制方向的算术、构造、解析、字符串测试src/ByteSizeLib.Tests/Binary/—— 二进制方向的同名四件套- 顶层
src/ByteSizeLib.Tests/—— 跨体系的公共行为
构建也很轻:dotnet build编译、dotnet test跑测试(见根目录 Makefile)。多目标框架定义在src/ByteSizeLib/ByteSizeLib.csproj,通过 netstandard 同时覆盖 .NET Core、.NET Framework 与 UWP。
新手避坑指南 ⚠️
- 别把 KB 和 KiB 混用:
FromKiloBytes(1024)是 1_024_000 字节,而FromKibiBytes(1024)是 1_048_576 字节,二者相差 2.4%。 - 升级 v1 老代码要小心:v1 中
KB = 1024 B,v2 起KB = 1000 B,需要 1024 语义时请改用FromKibiBytes。 ++/--自增自减以字节为单位,且源码 TODO 注释暗示作者自己也在考虑移除它。- 乘法/除法返回的仍是 ByteSize:
FromBytes(4) * FromBytes(2)得到 8 字节,语义上"字节乘字节"并不常见,使用时想清楚即可。
总结:这套设计的可复用之处
- 单一数据源:所有单位都是同一个
long的投影,永远不会出现"KB 属性与 Bytes 属性互相打架"; - partial struct 分文件:十进制与二进制各占一文件,互不干扰,阅读路径清晰;
- 常量即文档:比例与符号全部
public const,既可供外部复用,又自带说明; - 输出交给 LargestWholeNumber 系列属性:UI 展示一行代码搞定,还顺带支持本地化。
如果你正在写任何涉及容量、带宽、存储配额的 C# 程序,这套"一个长整型 + 双向换算表"的模式,几乎可以直接抄进自己的领域模型里。
【免费下载链接】ByteSizeByteSize is a utility class that makes byte size representation in code easier by removing ambiguity of the value being represented. ByteSize is to bytes what System.TimeSpan is to time.项目地址: https://gitcode.com/gh_mirrors/byt/ByteSize
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考