Unity/C#如何实现可在Inspector编辑的可序列化HashSet并解决相关报错
可序列化HashSet修复方案
报错根因
NullReferenceException:Unity序列化回调执行时机早于HashSet基类的完全初始化,此时遍历HashSet的枚举器会访问未初始化的内部字段触发空引用。ArgumentNullException:同样是初始化未完成的状态下调用Clear()方法,HashSet内部存储数组为null,触发空参数异常。- 原有第21行的重复校验逻辑完全冗余,且原有序列化逻辑为增量往List添加元素,会导致已经从
HashSet中移除的元素残留在序列化List中,造成数据不一致。
修复后完整实现
using System; using System.Collections.Generic; using UnityEngine; [Serializable] public class SerializableHashSet<T> : HashSet<T>, ISerializationCallbackReceiver { [SerializeField] private List<T> values = new List<T>(); public SerializableHashSet() : base() {} public SerializableHashSet(IEnumerable<T> collection) : base(collection) {} public void OnBeforeSerialize () { // 清空旧数据,直接用当前HashSet的内容覆盖List,保证完全同步 values.Clear(); foreach (var val in this) { values.Add(val); } } public void OnAfterDeserialize () { // 先清空当前HashSet,加异常捕获兼容未初始化的极端情况 try { Clear(); } catch (ArgumentNullException) { // 基类未初始化完成时,先初始化内部存储结构 Initialize(0); } // 把序列化List的内容写入HashSet,自动去重 foreach (var val in values) { // 兼容值类型和引用类型的默认值过滤 if (EqualityComparer<T>.Default.Equals(val, default)) continue; Add(val); } } }
如果需要在Inspector中正常显示和编辑,还需要添加对应的PropertyDrawer,把HashSet当成普通List渲染:
#if UNITY_EDITOR using UnityEditor; using UnityEngine; [CustomPropertyDrawer(typeof(SerializableHashSet<>))] public class SerializableHashSetDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { var valuesProp = property.FindPropertyRelative("values"); EditorGUI.PropertyField(position, valuesProp, label, true); } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { var valuesProp = property.FindPropertyRelative("values"); return EditorGUI.GetPropertyHeight(valuesProp, label, true); } } #endif
使用说明
直接声明对应类型的字段即可,示例:
public class TestScript : MonoBehaviour { public SerializableHashSet<string> stringHashSet; public SerializableHashSet<int> intHashSet; }
在Inspector中编辑时会自动处理重复值,运行时读写和原生HashSet完全一致。
内容的提问来源于stack exchange,提问作者PhilB
相关产品推荐
相关产品推荐

