Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

ISerializationCallbackReceiver throwing exceptions

Discussion in 'Scripting' started by arkano22, Jun 1, 2015.

  1. arkano22


    Sep 20, 2012
    Hi all!

    I´ve got this very simple implementation of a serializable hash set using ISerializationCallbackReceiver:

    Code (CSharp):
    2. [Serializable]
    3. public class SerializableHashSet<T> : HashSet<T>,ISerializationCallbackReceiver
    4. {
    6.     [SerializeField] private List<T> elements = new List<T>();
    8.     // save the set to a list
    9.     public void OnBeforeSerialize()
    10.     {
    11.         elements.Clear();
    12.         elements.AddRange(this);
    13.     }
    15.     // load set from list
    16.     public void OnAfterDeserialize()
    17.     {
    18.         Clear();
    19.         UnionWith(elements);
    20.     }
    21. }
    However this is constantly throwing "index out of range" when going into play mode:

    IndexOutOfRangeException: Array index is out of range.
    System.Collections.Generic.HashSet`1[T].Add (.T item)
    System.Collections.Generic.HashSet`1[T].UnionWith (IEnumerable`1 other)
    SerializableHashSet`1[T].OnAfterDeserialize () (at Assets/Model/Cloth/SerializableHashSet.cs:22)

    This seems to be a thread related issue, but I don`t have a clue what is exactly causing this. I only use one instance of this class in my whole project, which is instantiated in Awake(), and used in Update().

    I`ve also tried a serializable dictionary implemented in a similar way (two Lists instead of one, to hold keys and values respectively) and exceptions are being thrown too.

    any idea about what is going on?
    vexe likes this.
  2. Baste


    Jan 24, 2013
    Seems like a bug.This seems to be happening internally in HashSet.UnionWith (in a call to Add), but the docs states that only a ArgumentNullException can be thrown by the UnionWith method.

    Have you tried iterating over the elements array, and adding the elements one by one?
  3. arkano22


    Sep 20, 2012
    Hi Baste,

    Yes, in case of using a "foreach" instead, the same exception is thrown when reaching the "foreach" line.

    Even using the example dictionary implementation provided in the unity docs ( throws like crazy. In the docs they do warn you that modifying the object that is being serialized can confuse the serializer, and in the c# docs they also warn you that funky stuff can happen when accessing a hash set/dictionary from several threads at once, which i think is the cause of this error since unity serialization happens on a different thread.

    There must be a secret way of using ISerializationCallbackReceiver that i`m not aware of...o_O
  4. arkano22


    Sep 20, 2012
    Well, after some more testing, ISerializationCallbackReceiver seems fundamentally broken to me. I´ve managed to reproduce this problem with a very simple setup, and it happens every single time.

    I´ve issued a bug, will report here any news on the subject.
    vexe likes this.
  5. vexe


    May 18, 2013
    Thanks for issuing a bug! - I've been using a serializable dictionary very similar to this one here
    What's weird is that I've used this method for a couple of days and it was working just fine, but now out of no where I started getting these IndexOutOfRangeExceptions in the foreach (at the call to MoveNext to be exact)

    Regarding dictionary, here's what MSDN says:

    So most likely we're dealing with a threading issue.

    I changed the relation to be composition instead of inheritance and it seems to be working (at least for now), i.e.

    Code (csharp):
    2. public class SerializableDictionary<TK, TV> : ISerializationCallbackReceiver
    3. {
    4.     private Dictionary<TK, TV> _Dictionary;
    5.     [SerializeField] List<TK> _Keys;
    6.     [SerializeField] List<TV> _Values;
    8.     // wrapper methods, serialization, etc...
    9. }
    instead of:
    Code (csharp):
    2. public class SerializableDictionary<TK, TV> : Dictionary<TK, TV>, ISerializationCallbackReceiver
    3. {
    4.     [SerializeField] List<TK> _Keys;
    5.     [SerializeField] List<TV> _Values;
    7.     // serialization, etc...
    8. }
    But it just feels stupid and redundant to have to write unnecessary wrappers etc. It should work if we inherit Dictionary directly.

    I also tried locking on a lock object or the dictionary itself (this) before iterating, didn't make any difference.
    Last edited: Jun 20, 2015
  6. Peez-Machine


    Jul 30, 2013
    Lately I've been having issues with ISerializationCallbackReceiver in 5.2. I'm essentially unable to modify any lists that are fields of classes that implement the interface -- the list will appear in the editor, but the size will always stay at 0, regardless of what I set it to (in some cases I think it would go to 1, but no further). This has been true for the following cases:

    1) Serializable versions of non-serializable classes. In fact, I copied the SerializeableHashset code posted by the OP and this issue occurred.

    2) Non-generic classes that inherit from classes mentioned in (1). For example an IntSerializableHashset : SerializableHashset<int> {}

    3) Classes that implement the interface in order to serialize a field, not "itself." I've included some code for such a class, which should simply be packing/unpacking a dictionary to/from lists when serializing/deserializing.

    Code (csharp):
    2. [Serializable]
    3. public class DictHaver : ISerializationCallbackReceiver {
    4.     [SerializeField]
    5.     private List<int> keys; // = new List<int> ();  //initializing doesn't fix anything
    7.     [SerializeField]
    8.     private List<string> values; // = new List<string> ();
    10.     private Dictionary<int, string> d = new Dictionary<int, string>();
    12.     public Dictionary<int,string> D { get { return this.d; } }
    14.     public void OnBeforeSerialize(){
    15.         keys.Clear ();
    16.         values.Clear ();
    17.         foreach (KeyValuePair<int, string> pair in this.d) {
    18.             keys.Add (pair.Key);
    19.             values.Add (pair.Value);
    20.         }
    21.     }
    23.     public void OnAfterDeserialize(){
    24.         this.d.Clear ();
    26.         if (keys.Count != values.Count)
    27.             return;
    29.         for (int i = 0; i< keys.Count; i++)
    30.             this.d.Add (keys [i], values [i]);
    31.     }
    32. }

    I swear there was a time when such code worked as intended.

    However, I am able to create a List that implements ISerializationCallbackReceiver and stores its data in an underlying list field. Go figure.

    EDIT: Typically I use Vexe's VFW, and had the same issue with HashSets, but not Dictionaries. Looking through the Vexe code, I didn't find a FullSerializer for HashSet. Maybe that's the Vexe issue? (Just an aside, the rest of the post is about problems in vanilla Unity).
  7. Discipol


    May 6, 2015
    Hi! Any update on this? I need a hashset<t> to be visible in the inspector and updated with SetDirty, which is what the serializable thing you added to it, should do, but it doesn't. can't see it in the inspector and when I go to Play, its cleared :| Please help senpai :D