首页 > 解决方案 > 如何将可序列化的 HashSet 添加到 Unity/C#?

问题描述

我需要一个可以在 Inspector 中编辑的 HashSet。

我找到了这个解决方案......

在这里发布...

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 ()
    {
        var cur = new HashSet<T> (values);
        
        foreach (var val in this) {
            if (!cur.Contains (val)) {
                values.Add (val);
            }
        }
    }
    
    public void OnAfterDeserialize ()
    {
        Clear ();

        foreach (var val in values)
        {
            if (val != null)
            Add (val);
        }
    }
}

但是它会引发以下2个错误...

NullReferenceException: Object reference not set to an instance of an object
System.Collections.Generic.HashSet`1+Enumerator[T].MoveNext () (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
SerializableHashSet`1[T].OnBeforeSerialize () (at Assets/Scripts/Utilities/Collections/SerializableHashSet.cs:23)

ArgumentNullException: Value cannot be null.
Parameter name: array
System.Array.Clear (System.Array array, System.Int32 index, System.Int32 length) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Collections.Generic.HashSet`1[T].Clear () (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
SerializableHashSet`1[T].OnAfterDeserialize () (at Assets/Scripts/Utilities/Collections/SerializableHashSet.cs:32)

我也不确定我是否需要在第 21 行进行检查 - 首先肯定不会在内部 HashSet 中出现重复项?

标签: c#unity3dserializationdeserializationhashset

解决方案


一般来说,简单地从集合类型继承有点危险。

老实说甚至不确定,但我认为这里的问题与序列化程序没有正确调用构造函数有关。

相反,我会(即使它当然需要更多工作)有一个包含两个字段 aList<T>和 a的包装类HashSet<T>,然后实现相同的接口,将它们转发到HashSet字段,例如

[Serializable]
public class SerializableHashSet<T> :
    ISerializationCallbackReceiver,
    ISet<T>,
    IReadOnlyCollection<T>
{
    [SerializeField] private List<T> values = new List<T>();
    private HashSet<T> _hashSet = new HashSet<T>();


    #region Constructors

    // empty constructor required for Unity serialization
    public SerializableHashSet() { }

    public SerializableHashSet(IEnumerable<T> collection)
    {
        _hashSet = new HashSet<T>(collection);
    }

    #endregion Constructors


    #region Interface forwarding to the _hashset

    public int Count => _hashSet.Count;
    public bool IsReadOnly => false;
    public bool ISet<T>.Add(T item) => _hashSet.Add(item);
    public bool ICollection<T>.Remove(T item) => _hashSet.Remove(item);
    public void ExceptWith(IEnumerable<T> other) => _hashSet.ExceptWith(other);
    public void IntersectWith(IEnumerable<T> other) => _hashSet.IntersectWith(other);
    public bool IsProperSubsetOf(IEnumerable<T> other) => _hashSet.IsProperSubsetOf(other);
    public bool IsProperSupersetOf(IEnumerable<T> other) => _hashSet.IsProperSupersetOf(other);
    public bool IsSubsetOf(IEnumerable<T> other) => _hashSet.IsSubsetOf(other);
    public bool IsSupersetOf(IEnumerable<T> other) => _hashSet.IsSupersetOf(other);
    public bool Overlaps(IEnumerable<T> other) => _hashSet.Overlaps(other);
    public bool SetEquals(IEnumerable<T> other) => _hashSet.SetEquals(other);
    public void SymmetricExceptWith(IEnumerable<T> other) => _hashSet.SymmetricExceptWith(other);
    public void UnionWith(IEnumerable<T> other) => _hashSet.UnionWith(other);
    public void Clear() => _hashSet.Clear();
    public bool Contains(T item) => _hashSet.Contains(item);
    public void CopyTo(T[] array, int arrayIndex) => _hashSet.CopyTo(array, arrayIndex);
    Collection<T>.Add(T item) => _hashSet.Add(item);
    public IEnumerator<T> GetEnumerator() => _hashSet.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    #endregion Interface forwarding to the _hashset


    #region ISerializationCallbackReceiver implemenation

    public void OnBeforeSerialize()
    {
        var cur = new HashSet<T>(values);

        foreach (var val in this)
        {
            if (!cur.Contains(val))
            {
                values.Add(val);
            }
        }
    }

    public void OnAfterDeserialize()
    {
        Clear();

        foreach (var val in values)
        {
            Add(val);
        }
    }

    #endregion ISerializationCallbackReceiver implemenation
}

如果这是我无法判断的最佳方法,但它毫无例外地有效;)

请注意

在检查器中,您仍然可以有重复的条目,这些条目将被 HashSet 简单地忽略。在 HashSet 中删除的条目也将保留在 List 中!

此限制来自您的ISerializationCallbackReceiver实施。之所以这样做是因为 Inspector 在添加元素时只是复制了最后一个条目,否则根本不可能通过 Inspector 添加元素。除了实现自定义抽屉之外,没有真正的解决方法......


推荐阅读