【问题标题】:How can you add a Serializable HashSet to Unity / C#?如何将可序列化的 HashSet 添加到 Unity/C#?
【发布时间】:2021-12-08 18:08:15
【问题描述】:

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

我找到了this solution...

在这里发布...

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# unity3d serialization deserialization hashset


    【解决方案1】:

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

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

    相反,我会(尽管它当然需要更多工作)有一个包含两个字段的包装类,一个 List&lt;T&gt; 和一个 HashSet&lt;T&gt; 并行,然后实现相同的接口,将它们转发到 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 添加元素。除了实现自定义抽屉之外,没有其他方法可以解决这个问题......

    【讨论】:

    • 我收到此行的警告... public IEnumerator GetEnumerator() => _hashSet.GetEnumerator();也就是说... 装箱分配:从 'HashSet.Enumerator' 到 'IEnumerator' 的转换需要值类型的装箱
    • 我还收到以下编译器错误... Assets/Scripts/Progression/ProgressController.cs(20,48): error CS1061: 'SerializableHashSet' 不包含'Remove' 并且没有可访问的扩展方法 'Remove' 接受类型为 'SerializableHashSet' 的第一个参数(您是否缺少 using 指令或程序集引用?)
    • Assets/Scripts/UI/Balance of Power Adjustments Screen/BalanceOfPowerAdjustmentsScreenController.cs(1027,39):错误 CS0173:条件表达式的类型无法确定,因为 'System.Collections 之间没有隐式转换.Generic.HashSet' 和 'SerializableHashSet'
    • NullReferenceException:对象引用未设置为对象 System.Collections.Generic.HashSet1+Enumerator[T].MoveNext () (at &lt;351e49e2a5bf4fd6beabb458ce2255f3&gt;:0) SerializableHashSet1[T].OnBeforeSerialize () 的实例(在 Assets/Scripts/Utilities/Collections/SerializableHashSet. cs:21) UnityEditor.HostView:OnInspectorUpdate()(在 /Users/bokken/buildslave/unity/build/Editor/Mono/HostView.cs:354)
    • NullReferenceException: 对象引用未设置为对象 System.Collections.Generic.HashSet1+Enumerator[T].MoveNext () (at &lt;351e49e2a5bf4fd6beabb458ce2255f3&gt;:0) SerializableHashSet1[T].OnBeforeSerialize () 的实例(在 Assets/Scripts/Utilities/Collections/SerializableHashSet. cs:21) UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&) (at /Users/bokken/buildslave/unity/build/Modules/IMGUI/GUIUtility.cs:189)
    猜你喜欢
    • 2018-06-09
    • 2020-01-30
    • 1970-01-01
    • 2011-05-10
    • 1970-01-01
    • 2018-11-16
    • 2015-08-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多