c#字典查找抛出"索引超出了数组的范围"

Rye*_*ead 1 c# dictionary

我收到了一个错误报告,似乎来自以下代码:

public class AnimationChannelCollection : ReadOnlyCollection<BoneKeyFrameCollection>
{
        private Dictionary<string, BoneKeyFrameCollection> dict =
            new Dictionary<string, BoneKeyFrameCollection>();

        private ReadOnlyCollection<string> affectedBones;

       // This immutable data structure should not be created by the library user
        internal AnimationChannelCollection(IList<BoneKeyFrameCollection> channels)
            : base(channels)
        {
            // Find the affected bones
            List<string> affected = new List<string>();
            foreach (BoneKeyFrameCollection frames in channels)
            {
                dict.Add(frames.BoneName, frames);
                affected.Add(frames.BoneName);
            }
            affectedBones = new ReadOnlyCollection<string>(affected);

        }

        public BoneKeyFrameCollection this[string boneName]
        {           
            get { return dict[boneName]; }
        }
}
Run Code Online (Sandbox Code Playgroud)

这是读取字典的调用代码:

public override Matrix GetCurrentBoneTransform(BonePose pose)
    {
        BoneKeyFrameCollection channel =  base.AnimationInfo.AnimationChannels[pose.Name];       
    }
Run Code Online (Sandbox Code Playgroud)

这是创建字典的代码,在启动时发生:

// Reads in processed animation info written in the pipeline
internal sealed class AnimationReader :   ContentTypeReader<AnimationInfoCollection>
{
    /// <summary> 
    /// Reads in an XNB stream and converts it to a ModelInfo object
    /// </summary>
    /// <param name="input">The stream from which the data will be read</param>
    /// <param name="existingInstance">Not used</param>
    /// <returns>The unserialized ModelAnimationCollection object</returns>
    protected override AnimationInfoCollection Read(ContentReader input, AnimationInfoCollection existingInstance)
    {
        AnimationInfoCollection dict = new AnimationInfoCollection();
        int numAnimations = input.ReadInt32();

        /* abbreviated */

        AnimationInfo anim = new AnimationInfo(animationName, new AnimationChannelCollection(animList));

    }
}
Run Code Online (Sandbox Code Playgroud)

错误是:

指数数组的边界之外.

行:0

在System.Collections.Generic.Dictionary`2.FindEntry(TKey key)

在System.Collections.Generic.Dictionary`2.get_Item(TKey key)

at Xclna.Xna.Animation.InterpolationController.GetCurrentBoneTransform(BonePose pose)

我本来期望KeyNotFoundException有错误的键,但我得到"索引超出了数组的范围".我不明白我是如何从上面的代码中获得该异常的?

顺便说一下,代码是单线程的.

Sco*_*ain 11

"索引阵列的边界之外." System.Collections当文档说不应该抛出错误时,字典(或命名空间中的任何内容)上的错误总是由于违反线程安全性而导致.

System.Collections命名空间中的所有集合仅允许两个操作中的一个发生

  • 无限制的并发读者,0位作家.
  • 0位读者,1位作家.

您必须使用a来同步对字典的所有访问,ReaderWriterLockSlim这将提供上述确切的行为

private Dictionary<string, BoneKeyFrameCollection> dict =
            new Dictionary<string, BoneKeyFrameCollection>();
private ReaderWriterLockSlim dictLock = new ReaderWriterLockSlim();

public BoneKeyFrameCollection this[string boneName]
{           
    get 
    { 
        try
        {
            dictLock.EnterReadLock();
            return dict[boneName]; 
        }
        finally
        {
            dictLock.ExitReadLock();
        }
    }
}


 public void UpdateBone(string boneName, BoneKeyFrameCollection col)
 {  
    try
    {
        dictLock.EnterWriteLock();
        dict[boneName] = col; 
    }
    finally
    {
        dictLock.ExitWriteLock();
    }
 }
Run Code Online (Sandbox Code Playgroud)

或者替换你Dictionary<string, BoneKeyFrameCollection>ConcurrentDictionary<string, BoneKeyFrameCollection>

private ConcurrentDictionary<string, BoneKeyFrameCollection> dict =
            new ConcurrentDictionary<string, BoneKeyFrameCollection>();

 public BoneKeyFrameCollection this[string boneName]
 {           
    get 
    { 
        return dict[boneName];
    }
 }

 public void UpdateBone(string boneName, BoneKeyFrameCollection col)
 {  
    dict[boneName] = col;
 }
Run Code Online (Sandbox Code Playgroud)

更新:我真的不明白你所展示的代码是如何造成这种情况的.这是导致它被抛出的函数的源代码.

private int FindEntry(TKey key) {
    if( key == null) {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
    }

    if (buckets != null) {
        int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
        for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) {
            if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
        }
    }
    return -1;
}
Run Code Online (Sandbox Code Playgroud)

代码抛出a的唯一方法ArgumentOutOfRangeException是,如果您尝试索引buckets或中的非法记录entries.

因为您的密钥是a string并且字符串是不可变的,所以我们可以排除hashcode在将密钥放入字典后更改的密钥的值.剩下的就是buckets[hashCode % buckets.Length]打电话和几个entries[i]电话.

唯一buckets[hashCode % buckets.Length]可能失败的方法是bucketsbuckets.Length属性调用和this[int index]索引器调用之间替换实例.唯一的一次buckets被替换时Resize在内部被称为Insert,Initialize由构造/第一个电话打电话Insert,或到一个呼叫OnDeserialization.

唯一Insert被调用的地方是setter for this[TKey key],public Addfunction和inside OnDeserialization.buckets要替换的唯一方法是,如果我们在调用FindEntry期间在另一个线程上发生调用的同一时间调用三个列出的函数之一buckets[hashCode % buckets.Length].

我们唯一可以得到一个糟糕的entries[i]电话的方法是,如果entries我们换掉了(遵循相同的规则buckets),或者我们得到一个糟糕的价值i.获得错误值的唯一方法i是if entries[i].next返回错误的值.从得到一个坏值的唯一方法entries[i].next是有并发操作时怎么回事Insert,Resize或者Remove.

我唯一能想到的是要么在OnDeserialization调用时出错,要么在反序列化之前有错误的数据,或者有更多的代码AnimationChannelCollection会影响你没有向我们展示的字典.