文章目录
- 前言
- 1 项目结构
- 1.1 整体
- 1.2 代码
- 2 实现
- 2.1 Processor
- 2.1.1 BaseType
- 2.1.2 CollectionType
- 2.1.3 CustomType
- 2.2 ByteFormatter
- 2.3 ByteHelper
- 3 使用
前言
BinaryFormatter 类可以将 C# 类对象快速转换为字节数组数据。
在网络开发时,不会使用 BinaryFormatter 进行数据序列化和反序列化。因为客户端和服务端的开发语言多数情况下不同,BinaryFormatter 序列化的数据无法兼容其它语言。
因此,需要自定义序列化方式。
BinaryFormatter 参考链接:2023-05-27 Unity 2进制4——类对象的序列化与反序列化_unity 二进制序列化-CSDN博客。
- 项目链接:https://github.com/zheliku/ByteHelper。
- Unity 版本:6000.0.42f1。
1 项目结构
1.1 整体

- Scenes(示例场景)
- Scripts(脚本)
- ByteHelper(工具脚本)
- Test(测试脚本)
1.2 代码

- Processor(存放对应类型的 Processor)
- BaseType(基本类型的 Processor)
- CollectionType(集合类型的 Processor)
- CustomType(自定义类型的 Processor)
- Processor.cs(抽象基类,用于处理对象和字节数组之间的转换)
- ByteFormatter.cs(存储所有 Processor,并依据类型进行 Write 与 Read)
- ByteHelper.cs(封装序列化 API)
- ReflectionExtension.cs(反射方法拓展)
2 实现
2.1 Processor
一个 Processor 用于处理一种类型的序列化,其包含以下 3 种方法:
-
GetBytesLength
获取对象在字节数组中占用的字节数。
-
Write
将对象写入 bytes 数组。
-
Read
从 bytes 数组中读取对象。
// 抽象类 Processor,用于处理对象和字节数组之间的转换
public abstract class Processor
{public abstract int GetBytesLength(object value);public abstract int Write(byte[] bytes, object value, int index);public abstract int Read(byte[] bytes, int index, out object value);
}
使用泛型版本标识每个 Processor 处理的类型:
public abstract class Processor<TValue> : Processor
{public abstract int GetBytesLength(TValue value);public abstract int Write(byte[] bytes, TValue value, int index);public abstract int Read(byte[] bytes, int index, out TValue value);public override int GetBytesLength(object value){return GetBytesLength((TValue) value);}public override int Write(byte[] bytes, object value, int index){return Write(bytes, (TValue) value, index);}public override int Read(byte[] bytes, int index, out object value){int result = Read(bytes, index, out TValue typedValue);value = typedValue;return result;}
}
2.1.1 BaseType
以 int、string 类型为例:
IntProcessor
-
GetBytesLength
int 类型使用 4 个子节存储,可直接返回 4,也可使用
sizeof(int)
。 -
Write
直接转换为子节,写入 bytes 中的 index 位置。返回值为写入后下一处的位置。
-
Read
直接将 bytes 中 index 位置的数据读取。返回值为读取后下一处的位置。
public class IntProcessor : Processor<int>
{public override int GetBytesLength(int value){return sizeof(int);}public override int Write(byte[] bytes, int value, int index){BitConverter.GetBytes(value).CopyTo(bytes, index);return index + sizeof(int);}public override int Read(byte[] bytes, int index, out int value){value = BitConverter.ToInt32(bytes, index);return index + sizeof(int);}
}
StringProcessor
string 类型长度可变,因此需要先写入长度(int 类型),再写入内容。
- GetBytesLength:int 长度 + 字符串长度。
- Write:先写入长度,后写入内容。
- Read:先读取长度,后读取内容。
public class StringProcessor : Processor<string>
{public override int GetBytesLength(string value){return sizeof(int) + value.Length;}public override int Write(byte[] bytes, string value, int index){BitConverter.GetBytes(value.Length).CopyTo(bytes, index);Encoding.UTF8.GetBytes(value).CopyTo(bytes, index + sizeof(int));return index + sizeof(int) + value.Length;}public override int Read(byte[] bytes, int index, out string value){int length = BitConverter.ToInt32(bytes, index);value = Encoding.UTF8.GetString(bytes, index + sizeof(int), length);return index + sizeof(int) + length;}
}
2.1.2 CollectionType
以 ICollectionProcessor 为例,泛型集合类型需要记录集合本身的 Type 与元素的 Type,因此 ICollectionProcessor 具有 2 个泛型参数。
-
GetBytesLength
集合长度可变,因此也先写入长度,后顺序写入集合元素。
-
Write
先写入长度,后顺序写入集合元素。
使用
ByteFormatter.Write
方法,依据元素类型,自动调用对应的 Processor 写入内容。 -
Read
先读取长度,后顺序读取集合元素。
使用
ByteFormatter.Read
方法,依据元素类型,自动调用对应的 Processor 读取内容。
public class ICollectionProcessor<TCollection, TValue> : Processor<TCollection> where TCollection : ICollection<TValue>
{public override int GetBytesLength(TCollection value){var length = sizeof(int);foreach (var item in value){length += ByteFormatter.GetBytesLength(item);}return length;}public override int Write(byte[] bytes, TCollection value, int index){int count = value.Count;// 写长度BitConverter.GetBytes(count).CopyTo(bytes, index);index += sizeof(int); // 留 1 个 int 位置用于写长度// 写内容foreach (var item in value){index = ByteFormatter.Write(bytes, item, index);}return index;}public override int Read(byte[] bytes, int index, out TCollection value){// 1. 读取长度(元素数量)int length = BitConverter.ToInt32(bytes, index);index += sizeof(int);// 2. 读取内容value = (TCollection) Activator.CreateInstance(typeof(TCollection));for (int i = 0; i < length; i++){index = ByteFormatter.Read(bytes, index, typeof(TValue), out var item);value.Add((TValue) item);}return index;}
}
2.1.3 CustomType
自定义类型默认序列化所有的字段,且需要添加 [ByteSerializable]
特性。
-
GetBytesLength
长度可变,因此也先写入长度,后顺序写入字段。
-
Write
先写入长度,后使用反射获取所有字段信息,依次写入字段内容。
反射方法
value.GetFieldValues()
在 ReflectionExtension.cs 中封装,默认获取所有字段的值。使用
ByteFormatter.Write
方法,依据字段类型,自动调用对应的 Processor 写入。 -
Read
先读取长度,后使用反射获取所有字段信息,依次读取字段内容。
反射方法
obj.GetFieldInfos()
在 ReflectionExtension.cs 中封装,默认获取所有字段的信息。使用
ByteFormatter.Read
方法,依据字段类型,自动调用对应的 Processor 读取。考虑到值类型,在读取时需要装箱,否则反射赋值时,会将内容赋值给 fieldInfo.SetValue 方法中的临时变量。
public class CustomTypeProcessor<TValue> : Processor<TValue>
{public override int GetBytesLength(TValue value){return sizeof(int) + value.GetFieldValues().Sum(v => ByteFormatter.GetBytesLength(v));}public override int Write(byte[] bytes, TValue value, int index){// 先写长度,以便读取BitConverter.GetBytes(GetBytesLength(value)).CopyTo(bytes, index);index += sizeof(int);var fieldValues = value.GetFieldValues();foreach (var fieldValue in fieldValues){index = ByteFormatter.Write(bytes, fieldValue, index);}return index;}public override int Read(byte[] bytes, int index, out TValue value){var obj = (object) Activator.CreateInstance<TValue>(); // 装箱,以防 TValue 为值类型index += sizeof(int);var fieldInfos = obj.GetFieldInfos();foreach (var fieldInfo in fieldInfos){index = ByteFormatter.Read(bytes, index, fieldInfo.FieldType, out var v);fieldInfo.SetValue(obj, v);}value = (TValue) obj; // 拆箱,还原 valuereturn index;}
}
2.2 ByteFormatter
管理所有类型的 Processor,使用字典存储预置基础类型:
public class ByteFormatter
{// 定义字典,用于存储预置类型的处理器public static readonly Dictionary<Type, object> PRIMITIVE_PROCESSORS = new Dictionary<Type, object>(){{ typeof(int), new IntProcessor() },{ typeof(short), new ShortProcessor() },{ typeof(long), new LongProcessor() },{ typeof(float), new FloatProcessor() },{ typeof(double), new DoubleProcessor() },{ typeof(bool), new BoolProcessor() },{ typeof(char), new CharProcessor() },{ typeof(byte), new ByteProcessor() },{ typeof(string), new StringProcessor() },};...
}
依据传入类型,提供对应的 Processor,若不存在则创建。
- 在预置类型的字典中,直接读取
public class ByteFormatter
{// 根据类型获取对应的处理器public static Processor<T> GetProcessor<T>(){return (Processor<T>) GetProcessor(typeof(T));}public static Processor GetProcessor(Type type){// 在预置类型的字典中,直接读取if (PRIMITIVE_PROCESSORS.TryGetValue(type, out object value)) {return (Processor) value;}}
}
-
优先处理字典类型,并且先处理 KeyValuePair。
若字典中存在,则直接读取,否则创建 Processor 添加到字典中再返回。
// 如果类型是 KeyValuePair,则使用 KeyValuePairProcessor
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{var processorType = typeof(KeyValuePairProcessor<,>).MakeGenericType(type.GetGenericArguments());var processor = (Processor) Activator.CreateInstance(processorType);PRIMITIVE_PROCESSORS.Add(type, processor); // 添加到字典中return processor;
}// 如果类型是字典,则使用 IDictionaryProcessor
if (type.IsAssignableToGenericInterface(typeof(IDictionary<,>)))
{var processorType = typeof(IDictionaryProcessor<,,>).MakeGenericType(type, type.GetGenericArguments()[0], type.GetGenericArguments()[1]);var processor = (Processor) Activator.CreateInstance(processorType);PRIMITIVE_PROCESSORS.Add(type, processor); // 添加到字典中return processor;
}
-
对于集合类型与用户自定义类型,同理。
对于用户自定义类型,需要继承
[ByteSerializable]
特性。也可以根据需要自定义其他方式。
// 如果是集合类型,则使用 ICollectionProcessor
if (type.IsAssignableToGenericInterface(typeof(ICollection<>)))
{var processorType = typeof(ICollectionProcessor<,>).MakeGenericType(type, type.GetGenericArguments()[0]);var processor = (Processor) Activator.CreateInstance(processorType);PRIMITIVE_PROCESSORS.Add(type, processor); // 添加到字典中return processor;
}// 如果拥有 ByteSerializableAttribute 特性,则使用 CustomTypeProcessor
if (type.HasAttribute<ByteSerializableAttribute>())
{var processorType = typeof(CustomTypeProcessor<>).MakeGenericType(type);var processor = (Processor) Activator.CreateInstance(processorType);PRIMITIVE_PROCESSORS.Add(type, processor); // 添加到字典中return processor;
}
提供 Write 和 Read 方法,依据参数类型,自动获取对应 Processor 处理。
public static int Write<T>(byte[] bytes, T value, int index)
{return GetProcessor<T>().Write(bytes, value, index);
}public static int Write(byte[] bytes, object value, int index)
{return GetProcessor(value.GetType()).Write(bytes, value, index);
}public static int Read<T>(byte[] bytes, int index, out T value)
{return GetProcessor<T>().Read(bytes, index, out value);
}public static int Read(byte[] bytes, int index, Type type, out object value)
{return GetProcessor(type).Read(bytes, index, out value);
}
2.3 ByteHelper
封装序列化方法。
public class ByteHelper
{public const string EXTENSION = ".bytes";public static string BinarySavePath { get; set; } = Application.persistentDataPath + "/Binary";public static byte[] Serialize<TData>(TData data){var processor = ByteFormatter.GetProcessor<TData>();var bytes = new byte[processor.GetBytesLength(data)];processor.Write(bytes, data, 0);return bytes;}public static TData Deserialize<TData>(byte[] bytes){ByteFormatter.Read<TData>(bytes, 0, out var value);return value;}public static void SaveBytes<TData>(string filePath, TData data, string extension = EXTENSION){string fullPath = Path.Combine(BinarySavePath, filePath);fullPath = Path.ChangeExtension(fullPath, EXTENSION);var directory = Path.GetDirectoryName(fullPath);if (directory != null && !Directory.Exists(directory)){Directory.CreateDirectory(directory);}byte[] bytes = Serialize(data);File.WriteAllBytes(fullPath, bytes);#if UNITY_EDITORUnityEditor.AssetDatabase.Refresh();
#endif}public static TData LoadBytes<TData>(string filePath, string extension = ByteHelper.EXTENSION){string fullPath = Path.Combine(BinarySavePath, filePath);fullPath = Path.ChangeExtension(fullPath, EXTENSION);if (!File.Exists(fullPath)){ // 不存在文件,则警告,并返回默认值Debug.LogWarning($"ByteHelper: Can't find path \"{fullPath}\"");return default(TData);}byte[] bytes = File.ReadAllBytes(fullPath);return Deserialize<TData>(bytes);}
}
3 使用
以 CustomType 为例,打开 CustomType 场景,点击 “TestScript” 物体,右侧编辑你想要的数据。

运行场景,会自动序列化数据,对应脚本如下:
[Serializable] [ByteSerializable]
public class CustomData
{public int Id;private string _name;public List<int> List = new List<int>();public NestedData NestedData; // Supports nested typespublic string Name{get => _name;set => _name = value;}public override string ToString() {...}
}[Serializable] [ByteSerializable]
public struct NestedData
{public bool Bool;public override string ToString() {...}
}public class Test_CustomType : MonoBehaviour
{public Button Btn;public Text Text;[Header("Set Your CustomData")]public CustomData CustomData;public NestedData NestedData;private void Start(){CustomData.Name = "zheliku"; // 设置私有字段SerializeData();Text.text = "Serialize Data Success!";Btn.onClick.AddListener(OnClick);}// 序列化数据,保存到 CustomType 目录下。private void SerializeData(){ByteHelper.SaveBytes($"CustomType/{nameof(CustomData)}", CustomData);ByteHelper.SaveBytes($"CustomType/{nameof(NestedData)}", NestedData);}// 点击按钮时,显示反序列化数据public void OnClick(){Text.text = "CustomData: " + ByteHelper.LoadBytes<CustomData>($"CustomType/{nameof(CustomData)}") + "\n\n" +"NestedData: " + ByteHelper.LoadBytes<NestedData>($"CustomType/{nameof(NestedData)}");}
}
点击按钮,即可显示反序列化数据:

点击 “ByteHelper/Open Binary Folder” 可打开序列化数据存储目录。

CustomType 保存在 “CustomType” 目录下。

CustomData 的序列化内容如下:

根据需要可自行扩展 Processor。