- Home /
Better Dictionary? (serializable with more functionality)
So I'm looking for a dictionary that provides more features than the one that .NET provides. Specifically, I'm looking for extra stuff like insertion and removal of keys/values at a specified index. Also, it would be nice if it played nice with Unity's serialization system. Any ideas?
Answer by vexe · Sep 22, 2014 at 05:13 PM
[EDIT] Use this instead: http://forum.unity3d.com/threads/finally-a-serializable-dictionary-for-unity-extracted-from-system-collections-generic.335797/
You could use this implementation which uses two lists as keys and values - should provide the functionality you're asking. If you want it to play well with Unity's serialization system you have to subclass it, so:
[System.Serializable]
public class StrObjDict : KVPList<string, UnityEngine.Object> { }
and then use it normally, like:
public class Test : MonoBehaviour
{
public StrObjDict dict; // if not public, then annotate with SerializeField
}
Code:
[Serializable]
public class KVPList<TKey, TValue> : IDictionary<TKey, TValue>
{
[SerializeField]
protected List<TKey> keys;
[SerializeField]
protected List<TValue> values;
public List<TKey> Keys { get { return keys; } }
public List<TValue> Values { get { return values; } }
public int Count { get { return keys.Count; } }
public KVPList() : this(new List<TKey>(), new List<TValue>())
{
}
public KVPList(List<TKey> keys, List<TValue> values)
{
this.keys = keys;
this.values = values;
}
public TValue this[TKey key]
{
get
{
int index;
if (!TryGetIndex(key, out index))
{
throw new KeyNotFoundException(key.ToString());
}
return values[index];
}
set
{
int index;
if (!TryGetIndex(key, out index))
{
Add(key, value);
}
else values[index] = value;
}
}
public void SetKeyAt(int i, TKey value)
{
AssertIndexInBounds(i);
if (value != null && !value.Equals(keys[i]))
AssertUniqueKey(value);
keys[i] = value;
}
public TKey GetKeyAt(int i)
{
AssertIndexInBounds(i);
return keys[i];
}
public void SetValueAt(int i, TValue value)
{
AssertIndexInBounds(i);
values[i] = value;
}
public TValue GetValueAt(int i)
{
AssertIndexInBounds(i);
return values[i];
}
public KeyValuePair<TKey, TValue> GetPairAt(int i)
{
AssertIndexInBounds(i);
return new KeyValuePair<TKey, TValue>(keys[i], values[i]);
}
private void AssertIndexInBounds(int i)
{
if (!keys.InBounds(i))
throw new IndexOutOfRangeException("i");
}
public void Clear()
{
keys.Clear();
values.Clear();
}
public void Insert(int i, TKey key, TValue value)
{
AssertUniqueKey(key);
Assert.ArgumentNotNull(key, "Dictionary key cannot be null");
keys.Insert(i, key);
values.Insert(i, value);
}
private void AssertUniqueKey(TKey key)
{
if (ContainsKey(key))
throw new ArgumentException(string.Format("There's already a key `{0}` defined in the dictionary", key.ToString()));
}
public void Insert(TKey key, TValue value)
{
Insert(0, key, value);
}
public void Add(TKey key, TValue value)
{
Insert(Count, key, value);
}
public bool Remove(TKey key)
{
int index;
if (TryGetIndex(key, out index))
{
keys.RemoveAt(index);
values.RemoveAt(index);
return true;
}
return false;
}
public void RemoveAt(int i)
{
AssertIndexInBounds(i);
keys.RemoveAt(i);
values.RemoveAt(i);
}
public void RemoveLast()
{
RemoveAt(Count - 1);
}
public void RemoveFirst()
{
RemoveAt(0);
}
public bool TryGetValue(TKey key, out TValue result)
{
int index;
if (!TryGetIndex(key, out index))
{
result = default(TValue);
return false;
}
result = values[index];
return true;
}
public bool ContainsValue(TValue value)
{
return values.Contains(value);
}
public bool ContainsKey(TKey key)
{
return keys.Contains(key);
}
private bool TryGetIndex(TKey key, out int index)
{
return (index = keys.IndexOf(key)) != -1;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
for (int i = 0; i < Count; i++)
yield return new KeyValuePair<TKey, TValue>(keys[i], values[i]);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
ICollection<TKey> IDictionary<TKey, TValue>.Keys
{
get { return keys; }
}
bool IDictionary<TKey, TValue>.Remove(TKey key)
{
return Remove(key);
}
ICollection<TValue> IDictionary<TKey, TValue>.Values
{
get { return values; }
}
public void Add(KeyValuePair<TKey, TValue> item)
{
keys.Add(item.Key);
values.Add(item.Value);
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return ContainsKey(item.Key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
for (int i = arrayIndex; i < array.Length; i++)
{
array[i] = new KeyValuePair<TKey, TValue>(keys[i], values[i]);
}
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
}
public static class KVPDictionaryExtensions
{
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this KVPList<TKey, TValue> d)
{
return RTHelper.CreateDictionary(d.Keys, d.Values);
}
public static KVPList<TKey, TValue> ToKVPList<TKey, TValue>(this IDictionary<TKey, TValue> d)
{
return new KVPList<TKey, TValue>(d.Keys.ToList(), d.Values.ToList());
}
}
Edit:
Sorry forgot to mention about RTHelper, it's just a static utility class I use:
public static class RTHelper
{
/// <summary>
/// Creates a dictionary out of the passed keys and values
/// </summary>
public static Dictionary<T, U> CreateDictionary<T, U>(IEnumerable<T> keys, IList<U> values)
{
return keys.Select((k, i) => new { k, v = values[i] }).ToDictionary(x => x.k, x => x.v);
}
}
InBounds is just an extension method:
public static class IListExtensions
{
public static bool InBounds<T>(this IList<T> list, int i)
{
return i > -1 && i < list.Count;
}
}
You could replace the code if you don't want to add a helper and extensions static classes.
Don't forget your usings:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
thanks, but I'm getting some errors what is keys.InBounds? and what is RTHelper?
Question converted to comment. Please use the "add new comment" button when replying to an answer.
thanks, that's exactly what I want. is there anyway to skip the subclassing phase?
With Unity's current serialization system? no... it has very poor serialization support for generics, you have to subclass :| - You could take a look at ShowEmAll, it provides better serialization for generics