Search Unity

4.5 Dictionary

Discussion in 'Scripting' started by LightStriker, Jun 7, 2014.

  1. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Because Unity 4.5 now offer serialization callback, it's now possible to serialize dictionaries without horrible fancy hacking. We are now able to have dictionary that handles their own serialization without having to write anything extra. And because rewriting a generic collection with all its proper interface is always one major pain in the ass, here's a fully implemented one;

    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Diagnostics;
    6. using System.Linq;
    7. using System.Runtime.InteropServices;
    8. using System.Runtime.Serialization;
    9. using System.Text;
    10.  
    11. using UnityEngine;
    12.  
    13. #if UNITY_4_5
    14. [Serializable]
    15. [ComVisible(false)]
    16. [DebuggerDisplay("Count = {Count}")]
    17. public class UDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback, ISerializationCallbackReceiver
    18. {
    19.   [SerializeField]
    20.   private List<TKey> keys = new List<TKey>();
    21.  
    22.   [SerializeField]
    23.   private List<TValue> values = new List<TValue>();
    24.  
    25.   [NonSerialized]
    26.   private Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();
    27.  
    28.   #region Implementation of ISerializationCallbackReceiver
    29.   public void OnAfterDeserialize()
    30.   {
    31.     dictionary.Clear();
    32.     for (int i = 0; i < keys.Count; i++)
    33.     dictionary.Add(keys[i], values[i]);
    34.   }
    35.  
    36.   public void OnBeforeSerialize()
    37.   {
    38.     keys.Clear();
    39.     values.Clear();
    40.     foreach (KeyValuePair<TKey, TValue> pair in dictionary)
    41.     {
    42.       keys.Add(pair.Key);
    43.       values.Add(pair.Value);
    44.     }
    45.   }
    46.   #endregion
    47.  
    48.   #region Implementation of ISerializable
    49.   public void GetObjectData(SerializationInfo info, StreamingContext context)
    50.   {
    51.     dictionary.GetObjectData(info, context);
    52.   }
    53.   #endregion
    54.  
    55.   #region Implementation of IDeserializationCallback
    56.   public void OnDeserialization(object sender)
    57.   {
    58.     dictionary.OnDeserialization(sender);
    59.   }
    60.   #endregion
    61.  
    62.   #region Implementation IDictionary
    63.   public bool IsFixedSize
    64.   {
    65.     get { return false; }
    66.   }
    67.  
    68.   public ICollection<TKey> Keys
    69.   {
    70.     get { return dictionary.Keys; }
    71.   }
    72.  
    73.   ICollection IDictionary.Keys
    74.   {
    75.     get { return dictionary.Keys; }
    76.   }
    77.  
    78.   public ICollection<TValue> Values
    79.   {
    80.   get { return dictionary.Values; }
    81.   }
    82.  
    83.   ICollection IDictionary.Values
    84.   {
    85.     get { return dictionary.Values; }
    86.   }
    87.  
    88.   public TValue this[TKey key]
    89.   {
    90.     get { return dictionary[key]; }
    91.     set { dictionary[key] = value; }
    92.   }
    93.  
    94.   object IDictionary.this[object key]
    95.   {
    96.     get
    97.     {
    98.       if (!(key is TKey))
    99.         return null;
    100.  
    101.       return dictionary[(TKey)key];
    102.     }
    103.     set
    104.     {
    105.       if (!(key is TKey))
    106.         return;
    107.  
    108.       if (!(value is TValue))
    109.         return;
    110.  
    111.       dictionary[(TKey)key] = (TValue)value;
    112.     }
    113.   }
    114.  
    115.   public void Add(TKey key, TValue value)
    116.   {
    117.     dictionary.Add(key, value);
    118.   }
    119.  
    120.   void IDictionary.Add(object key, object value)
    121.   {
    122.     if (!(key is TKey))
    123.     return;
    124.  
    125.     if (!(value is TValue))
    126.      return;
    127.  
    128.     dictionary.Add((TKey)key, (TValue)value);
    129.   }
    130.  
    131.   public bool ContainsKey(TKey key)
    132.   {
    133.     return dictionary.ContainsKey(key);
    134.   }
    135.  
    136.   bool IDictionary.Contains(object key)
    137.   {
    138.     if (!(key is TKey))
    139.       return false;
    140.  
    141.     return dictionary.ContainsKey((TKey)key);
    142.   }
    143.  
    144.   public bool Remove(TKey key)
    145.   {
    146.     return dictionary.Remove(key);
    147.   }
    148.  
    149.   void IDictionary.Remove(object key)
    150.   {
    151.     if (!(key is TKey))
    152.       return;
    153.  
    154.     dictionary.Remove((TKey)key);
    155.   }
    156.  
    157.   public bool TryGetValue(TKey key, out TValue value)
    158.   {
    159.     return dictionary.TryGetValue(key, out value);
    160.   }
    161.  
    162.   IDictionaryEnumerator IDictionary.GetEnumerator()
    163.   {
    164.     return dictionary.GetEnumerator();
    165.   }
    166.   #endregion
    167.  
    168.   #region Implementation ICollection
    169.   public int Count
    170.   {
    171.     get { return dictionary.Count; }
    172.   }
    173.  
    174.   public bool IsReadOnly
    175.   {
    176.     get { return false; }
    177.   }
    178.  
    179.   public bool IsSynchronized
    180.   {
    181.     get { return false; }
    182.   }
    183.  
    184.   public object SyncRoot
    185.   {
    186.     get { return null; }
    187.   }
    188.  
    189.   public void Add(KeyValuePair<TKey, TValue> item)
    190.   {
    191.     dictionary.Add(item.Key, item.Value);
    192.   }
    193.  
    194.   public void Clear()
    195.   {
    196.     dictionary.Clear();
    197.   }
    198.  
    199.   public bool Contains(KeyValuePair<TKey, TValue> item)
    200.   {
    201.     return dictionary.ContainsKey(item.Key) && dictionary[item.Key].Equals(item.Value);
    202.   }
    203.  
    204.   void ICollection.CopyTo(Array array, int index) { }
    205.  
    206.   void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { }
    207.  
    208.   public bool Remove(KeyValuePair<TKey, TValue> item)
    209.   {
    210.     return dictionary.Remove(item.Key);
    211.   }
    212.   #endregion
    213.  
    214.   #region Implementation of IEnumerable
    215.   public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    216.   {
    217.     return dictionary.GetEnumerator();
    218.   }
    219.  
    220.   IEnumerator IEnumerable.GetEnumerator()
    221.   {
    222.     return dictionary.GetEnumerator();
    223.   }
    224.   #endregion
    225. }
    226. #endif
    227.  
    Example of implementation;

    Code (csharp):
    1.  
    2. public class AIExample_Dictionary : MonoBehaviour
    3. {
    4.   /// <summary>
    5.   /// Please note that Unity is still not friendly with generic dictionary.
    6.   /// Therefor, you need a non-generic wrapping class.
    7.   /// </summary>
    8.   [Serializable]
    9.   private class SFDictionary : UDictionary<string, float> { }
    10.  
    11.   [SerializeField]
    12.   private SFDictionary dictionary = new SFDictionary();
    13. }
    14.  
    UDictionary will be a core class in a future Advanced Inspector update that will properly support dictionary inspection.
     
    rakkarage, zDemonhunter99 and mangax like this.
  2. Cynicat

    Cynicat

    Joined:
    Jun 12, 2013
    Posts:
    290
    sweet! thanks!
     
    Last edited: Jul 1, 2014
  3. Patico

    Patico

    Joined:
    May 21, 2013
    Posts:
    886
    It's awsome!
     
  4. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Just noticed... You can probably dump

    Code (csharp):
    1. using System.Diagnostics;
    2. using System.Linq;
    3. using System.Text;
     
  5. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    Cool Interface example. Thanks for advice.

    But, you use two List for showing a Dictionary data? And only is readable. We can not edit Dictionary content from Inspector.

    ___________________________

    I create another code, a derived Dictionary class, where I have added a DictionaryElements class array with two Dictionary elements, key and value.

    From this array, we can edit a Dictionary from Inspector, and look at the Dictionary elements at runtime.

    Code (CSharp):
    1. //********************************************
    2. // script1.cs
    3. //********************************************
    4.  
    5. using UnityEngine;
    6. using System;
    7. using System.Collections.Generic;
    8.  
    9. [Serializable]
    10. public class SFDictionary : Dictionary<string,float> {
    11.  
    12.     [Serializable]
    13.     public class DictionaryElements{
    14.  
    15.         public string key;
    16.         public float value;
    17.  
    18.         public DictionaryElements(string _key, float _value){ this.key = _key; this.value = _value; }
    19.      
    20.     }
    21.  
    22.  
    23.     [SerializeField]
    24.     public DictionaryElements[] elements;
    25.  
    26.     private void UpdateElements(){
    27.  
    28.         string[] arrayString = new string[this.Count];
    29.         this.Keys.CopyTo(arrayString,0);
    30.         float[] arrayFloat = new float[this.Count];
    31.         this.Values.CopyTo(arrayFloat,0);
    32.  
    33.         this.elements = new DictionaryElements[this.Count];
    34.  
    35.         for(int i=0;i<this.Count;i++){ this.elements[i] = new DictionaryElements(arrayString[i],arrayFloat [i]); }
    36.  
    37.     }
    38.  
    39.     public void Start(){
    40.  
    41.         for(int i=0;i<this.elements.Length;i++){ base.Add(this.elements[i].key,this.elements[i].value); }
    42.  
    43.     }
    44.  
    45.     public void Add(string key, float value){
    46.  
    47.         base.Add(key,value);
    48.         UpdateElements();
    49.  
    50.     }
    51.  
    52.     public bool Remove(string key){
    53.  
    54.         if(base.Remove(key)){
    55.  
    56.             this.UpdateElements();
    57.             return true;
    58.  
    59.         }
    60.  
    61.         return false;
    62.  
    63.     }
    64.  
    65. }
    66.  
    67. public class script1 : MonoBehaviour {
    68.  
    69.     public SFDictionary dictionary = new SFDictionary();
    70.  
    71.  
    72.     void Start(){
    73.  
    74.         this.dictionary.Start();
    75.  
    76.     }
    77.  
    78.     void Update(){
    79.  
    80.         if(Input.GetKeyDown(KeyCode.Return)){
    81.  
    82.             this.dictionary.Add(Time.time.ToString(),Time.time);
    83.  
    84.         }
    85.  
    86.         if(Input.GetKeyDown(KeyCode.Backspace)){
    87.  
    88.             if(this.dictionary.Count>0){
    89.  
    90.                 string[] arrayString = new string[this.dictionary.Keys.Count];
    91.                 this.dictionary.Keys.CopyTo(arrayString,0);
    92.                 this.dictionary.Remove(arrayString[arrayString.Length-1]);
    93.  
    94.             }
    95.  
    96.         }
    97.  
    98.     }
    99.  
    100. }

    _____________________________


    But, I do not get it to show dictionary elements array in Inspector from a class that uses generic type parameter.
     
    Last edited: Jul 1, 2014
  6. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    No, you cannot display Dictionary in the Inspector. Because the class can now be serialized without fancy hacking doesn't mean Unity suddenly rewrote the inspector to support that type.

    You can - somewhat easily - write your own custom editor/property drawer to display and edit that type. Or you can go the way you did and hide the dictionary in background while displaying lists up front.

    Or you can use AssetStore packages that support dictionary types. (Ex.: http://i.imgur.com/0NYND7h.png )

    Yup, Unity is very bad with generic. You always need a non-generic top-wrapping class to be able to serialize properly, and in the same extension, be able to show up in the Inspector. The only type Unity is able to serialize with generic argument is List<>.
     
  7. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    This is very useful! Do you also have an editor script for this?
     
  8. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    oh. I'm gonna try IsGreen's post.
     
  9. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    I do... Check my signature for "Advanced Inspector".

    Dictionary of <int, Example_ComponentBase> where Example_ComponentBase is an abstract base class of sub-components;