Unity Serialized Dictionary `Index Out Of Range` după 12 elemente (Programare, C#, Dicționar, Serializare, Unity3D, Deserializare)

Agresor a intrebat.
a intrebat.

Actualizare: Aceasta este o eroare confirmată de Unity și a fost înregistrată.

Am creat un dicționar generic serializabil și unul specific pentru int:Object.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;

/// <summary>
/// Is what it sounds like
/// </summary>
[System.Serializable]
public class SerializableDictionary<TKey,TValue> : Dictionary<TKey,TValue>, ISerializationCallbackReceiver 
{
  [SerializeField]
  protected List<TKey> keys = new List<TKey>();

  [SerializeField]
  protected List<TValue> values = new List<TValue>();

  //Save the dictionary to lists
  public void OnBeforeSerialize()
  {
    keys.Clear();
    values.Clear();
    foreach(KeyValuePair<TKey,TValue> pair in this)
    {
        keys.Add(pair.Key);
        values.Add(pair.Value);
    }
  }

  //Load the dictionary from lists
  public void OnAfterDeserialize()
  {
    this.Clear();

    if(keys.Count != values.Count)
    {
      throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable."));
    }

    for(int i = 0; i<keys.Count; i++)
    {
      this.Add(keys[i], values[i]);
    }
  }
}

[System.Serializable]
public class DictionaryOfStringAndInt : SerializableDictionary<string, int> { }

[System.Serializable]
public class DictionaryOfIntAndSerializableObject : SerializableDictionary<int, ScriptableObject> 
{
}

Cu toate acestea, compilatorul o ia razna cu eroarea

IndexOutOutOfRangeException: Indexul matricei este în afara intervalului. System.Collections.Generic.Dictionary`2+Enumerator[System.Int32,UnityEngine.ScriptableObject].MoveNext ()

Din anumite motive, această linie

foreach(KeyValuePair<TKey,TValue> pair in this)

aruncă această eroare, dar NUMAI DUPĂ ce DictionaryOfIntAndSerializableObject depășește 12 obiecte. Acest lucru mă derutează complet.

Vă puteți gândi la un motiv pentru care această DictionaryOfIntAndSerializableObject aruncă o excepție în afara intervalului după 12 obiecte?

Actualizare Acest post are aceeași problemă: http://forum.unity3d.com/threads/serializeable-dictionary-exceptions-after-code-change.319285/

Explicații suplimentare

Am o bază de date GameObject care conține toate datele mele de model (tip). Și ceea ce fac este să analizez fișierele CSV în ScriptableObjects pe care le stochez în interiorul bazei de date GameObject. În acest fel, baza mea de date este compilată o singură dată în timpul execuției și o pot accesa liber atât în timpul execuției, cât și în editor. Acest obiect de bază de date este cel care are funcția DictionaryOfIntAndSerializableObject obiecte. Am câte unul pentru fiecare tabel (deci am unul pentru arme, armuri, etc.).

Aici este clasa de bază a tuturor obiectelor mele care pot fi scriptate (care sunt stocate ca valoare în dicționar):

public class M_Card : ScriptableObject

{

  public E_CardTypes cardTypeID;

  public int rankID;

  public int rarityID;

  public int qualityID;

  public int editionID;

  public Sprite sprite;

 public M_Card()
 {
    cardTypeID                  = 0;
    rankID                      = 0;
    qualityID                   = 0;
    sprite = null;
 }

}

Și iată codul în care (după analizarea unui rând din CSV) introduc o instanță de M_Card în dicționar:

 private void LoadActionCards()
 {
    List<Dictionary<string, object>> listData = CSVReader.Read("Actions");

    for (var i = 0; i < listData.Count; i++)
    {
      Dictionary<string, object> data = listData[i];

      string name = (string)data["Name"];
      M_ActionCard card = ScriptableObjectCreator.createActionCard(name);

      int cardTypeID  = (int)data["CardTypeID"];
      card.cardTypeID = (E_CardTypes)cardTypeID;

      AssignActionCardValues(card, data);

      Database.ACTION_CARD_LIST.Add(card);

      // THIS IS WHERE I MAP THE TYPEID TO THE M_CARD OBJECT
      // THIS IS WHERE THE OBJECT IS ADDED TO THE DICTIONARY
      Database.ACTION_CARD_MAP[card.ID] = card;
    }
  }

Deci, în acest moment am 4 tabele de date (arme, căști, armuri, clase). Totuși, dacă oricare dintre aceste tabele are mai mult de 12 elemente, primesc acea eroare 100% la al 13-lea element. Încă investighez, dar iată cum se adaugă cheile în dicționar.

Rețineți că M_HelmetCard este la fel ca un M_ActionCard și ambele sunt doar subclase goale ale M_Card.

Iată câteva imagini pentru a adăuga o dovadă în plus. În prima am toate cele 16 căști. Apare eroarea

Apoi, aici fac în așa fel încât să fie doar 12 și eroarea a dispărut.

Orice listă care ajunge la mai mult de 12 erori iese. Elementele reale nu contează (deci nu este ca și cum un anumit al 13-lea element este corupt, 13 din aceleași elemente tot provoacă această eroare).

Foarte nedumerit în acest moment.

(M-am gândit că poate problema este că am avut un Sprite clasă care era serializată și care conținea prea multe date, dar când am scăpat de ea, problema a persistat).

Actualizare Aici este un link către un proiect de probă care are această problemă (tot ce este irelevant a fost eliminat). https://www.mediafire.com/?axxn3u823cm7c59

Pentru a produce eroarea: Faceți clic pe butonul database în heiric și faceți clic pe Update Database butonul .

Apoi apăsați play și veți vedea eroarea.

Apoi, dacă doriți să vedeți cum este al 13-lea obiect, deschideți fișierul CSV Helmets (AssetsResourcesExcelData) și eliminați cel de-al 13-lea element. Apoi actualizați din nou baza de date. Apoi apăsați play. Eroarea va dispărea!

Acesta este un WTF major 😕

Comentarii

  • However the compiler goes crazy with the error... Vrei să spui că primești o eroare de compilare? Sau primești asta la execuție? –  > Por dotnetom.
  • Pare suspect pentru mine… int value = keys[Random.Range(0, keys.Count)]; în special Random parte… –  > Por Ian.
  • Da, nu-ți face griji în legătură cu RandomID (nici măcar nu este folosit în prezent :P). –  > Por Aggressor.
  • @dotnetom Primesc o eroare atât în timpul execuției, cât și fără să fie apăsat butonul de redare. De fapt, uneori (la întâmplare) compilatorul emite acea eroare non stop până când reîncarc scena. Voi face un link în răspunsul meu la postarea altcuiva cu aceeași problemă. –  > Por Aggressor.
  • Am citit aici și acolo că Dictionary nu poate fi serializat în C#. Ar putea fi vorba de un conflict cu interfața? Dacă în loc să moștenești din Dictionary, ai avea un Dictionary în clasa ta ca un container? –  > Por Everts.
1 răspunsuri
mediocritate

M-am lovit de aceeași problemă. Se pare că serializarea lui Unity rupe uneori internalele dicționarului serializat, astfel încât valorile suplimentare nu pot fi adăugate.

În cazul meu am scăpat de aceste erori prin implementarea metodei IDictionary pe interfața SerializableDictionary și delegând funcționalitatea acesteia către o entitate privată Dictionary care nu este atins de serializarea Unity.

Iată soluția mea generată cu ajutorul Visual Studio

using UnityEngine;
using System.Collections.Generic;
using System.Collections;

[System.Serializable]
public class SerializableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ISerializationCallbackReceiver
{
    [SerializeField]
    private List<TKey> keys = new List<TKey>();

    [SerializeField]
    private List<TValue> values = new List<TValue>();

    private Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

    public ICollection<TKey> Keys
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary).Keys;
        }
    }

    public ICollection<TValue> Values
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary).Values;
        }
    }

    public int Count
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary).Count;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary).IsReadOnly;
        }
    }

    public TValue this[TKey key]
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary)[key];
        }

        set
        {
            ((IDictionary<TKey, TValue>)dictionary)[key] = value;
        }
    }

    public void OnBeforeSerialize()
    {
        keys.Clear();
        values.Clear();
        foreach (KeyValuePair<TKey, TValue> pair in this)
        {
            keys.Add(pair.Key);
            values.Add(pair.Value);
        }
    }

    public void OnAfterDeserialize()
    {
        dictionary = new Dictionary<TKey, TValue>();

        if (keys.Count != values.Count)
        {
            throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable.", keys.Count, values.Count));
        }

        for (int i = 0; i < keys.Count; i++)
        {
            Add(keys[i], values[i]);
        }
    }

    public void Add(TKey key, TValue value)
    {
        ((IDictionary<TKey, TValue>)dictionary).Add(key, value);
    }

    public bool ContainsKey(TKey key)
    {
        return ((IDictionary<TKey, TValue>)dictionary).ContainsKey(key);
    }

    public bool Remove(TKey key)
    {
        return ((IDictionary<TKey, TValue>)dictionary).Remove(key);
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        return ((IDictionary<TKey, TValue>)dictionary).TryGetValue(key, out value);
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        ((IDictionary<TKey, TValue>)dictionary).Add(item);
    }

    public void Clear()
    {
        ((IDictionary<TKey, TValue>)dictionary).Clear();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return ((IDictionary<TKey, TValue>)dictionary).Contains(item);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        ((IDictionary<TKey, TValue>)dictionary).CopyTo(array, arrayIndex);
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        return ((IDictionary<TKey, TValue>)dictionary).Remove(item);
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return ((IDictionary<TKey, TValue>)dictionary).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IDictionary<TKey, TValue>)dictionary).GetEnumerator();
    }
}

Comentarii

  • Eu am găsit o soluție diferită, dar vă mulțumesc pentru că ați împărtășit. Voi revedea ceea ce aveți aici dacă va fi nevoie! –  > Por Aggressor.