I was needing a serializeable dictionary, and after searching I have found this thread
In that thread there are links to other threads of people who had weird issues with ISerializationCallbackReceiver that can be threading issues.
The documents
warn about thread problems and states “You’re strongly recommended to only use this interface for the example use case mentioned above: serializing something yourself that Unity doesn’t know how to.”
Then below they gave a example script of a serializeable dictionary, which leads me to believe that there should be no issues with doing things similar to how they are doing it. (However, they are inheriting MonoBehaviour, which I do not want to do. Is that very important?)
In the thread at the top of the post where the user vexe posted a script of a serializeable dictionary, while it may or may not work, it looks far to scary for me and the user lordofduct posted an alternative which is much easier for me to digest, and is also done in a similar way to the unity document above (except lordofduct is using arrays, which I am not sure if that is needed or not? I replaced it with lists so I can use Clear to avoid garbage as well as changed a few other things in the OnBeforeDeserialize and OnAfterDeserialize).
I end up with a script like this
Click for code
using System;
using UnityEngine;
using System.Collections.Generic;
namespace Custom.Collections
{
[Serializable]
public class SerializeableDictionary<TKey, TValue> : DrawableDictionary, IDictionary<TKey, TValue>, ISerializationCallbackReceiver
{
[SerializeField] List<TKey> keys = new List<TKey>();
[SerializeField] List<TValue> values = new List<TValue>();
[NonSerialized] Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
if(dictionary == null) return;
CheckForNullList();
keys.Clear();
values.Clear();
Dictionary<TKey, TValue>.Enumerator enumerator = dictionary.GetEnumerator();
while(enumerator.MoveNext())
{
keys.Add(enumerator.Current.Key);
values.Add(enumerator.Current.Value);
}
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
CheckForNullList();
if(dictionary == null)
{
dictionary = new Dictionary<TKey, TValue>();
}else{
dictionary.Clear();
}
if(keys.Count != values.Count)
{
int difference = keys.Count - values.Count;
for(int i = 0; i < difference; i++)
{
values.Add(default(TValue));
}
}
for(int i = 0; i < keys.Count; i++)
{
dictionary.Add(keys[i], values[i]);
}
keys.Clear();
values.Clear();
}
void CheckForNullList()
{
if(keys.IsNull()) keys = new List<TKey>();
if(values.IsNull()) values = new List<TValue>();
}
#region IDictionary Interface
public int Count
{
get { return (dictionary != null) ? dictionary.Count : 0; }
}
public void Add(TKey key, TValue value)
{
if (dictionary == null) dictionary = new Dictionary<TKey, TValue>();
dictionary.Add(key, value);
}
public bool ContainsKey(TKey key)
{
if (dictionary == null) return false;
return dictionary.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get
{
if (dictionary == null) dictionary = new Dictionary<TKey, TValue>();
return dictionary.Keys;
}
}
public bool Remove(TKey key)
{
if (dictionary == null) return false;
return dictionary.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
if(dictionary == null)
{
value = default(TValue);
return false;
}
return dictionary.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get
{
if (dictionary == null) dictionary = new Dictionary<TKey, TValue>();
return dictionary.Values;
}
}
public TValue this[TKey key]
{
get
{
if (dictionary == null) throw new KeyNotFoundException();
return dictionary[key];
}
set
{
if (dictionary == null) dictionary = new Dictionary<TKey, TValue>();
dictionary[key] = value;
}
}
public void Clear()
{
if (dictionary != null) dictionary.Clear();
}
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
if (dictionary == null) dictionary = new Dictionary<TKey, TValue>();
(dictionary as ICollection<KeyValuePair<TKey, TValue>>).Add(item);
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
if (dictionary == null) return false;
return (dictionary as ICollection<KeyValuePair<TKey, TValue>>).Contains(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
if (dictionary == null) return;
(dictionary as ICollection<KeyValuePair<TKey, TValue>>).CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
if (dictionary == null) return false;
return (dictionary as ICollection<KeyValuePair<TKey, TValue>>).Remove(item);
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
{
get { return false; }
}
public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
{
if (dictionary == null) return default(Dictionary<TKey, TValue>.Enumerator);
return dictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
if (dictionary == null) return System.Linq.Enumerable.Empty<KeyValuePair<TKey, TValue>>().GetEnumerator();
return dictionary.GetEnumerator();
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
if (dictionary == null) return System.Linq.Enumerable.Empty<KeyValuePair<TKey, TValue>>().GetEnumerator();
return dictionary.GetEnumerator();
}
#endregion
}
public abstract class DrawableDictionary{}
}
So my concern is, is this all really safe?
We can just focus on the unity docs. We see that in
OnBeforeSerialize we are clearing the lists, taking whats currently in our dictionary and putting it in the lists.
Since OnBeforeSerialize and OnAfterSerialize are in a different thread, is it not possible that as the dictionary elements are being added to the lists, somewhere else there is a method in Update that has decided to clear the dictionary, but the serializer already stored lots of what was already in the dictionary and on OnAfterSerialize it will add it back. Now when I thought I cleared the dictionary, it actually still has stuff in it.
The docs say “Another caveat is that serialization happens more often than you might think. When a MonoBehaviour gets cloned through Instantiate() at runtime, it gets deserialized and serialized.”. Is that saying that at runtime, the only time serialization happens is when we call Instantiate()? Or is it saying that is just one of many others…
Is it that there would be a problem, however since serialization only happens at things like Instantiate, its going to maybe run the serialization in the main thread or something?
Is the only concern for when doing editor stuff?
@lordofduct you mentioned in this post Finally, a serializable dictionary for Unity! (extracted from System.Collections.Generic) - Unity Engine - Unity Discussions “…Oh, and I have ran into these same exceptions as well. So in the cases where it can’t be avoided, you have to use it and you don’t have full control of everything…”
Are you basically saying we will just have to hope for the best? Also, Why did you use arrays and not lists?
Any help is appreciated!