字典查找抛出“索引在数组边界之外”

本文关键字:边界 数组 索引 查找 字典 | 更新日期: 2023-09-27 18:12:00

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

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]; }
        }
}

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

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

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

// 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));
    }
}

错误是:

索引在数组边界之外。

线:0

System.Collections.Generic.Dictionary的2。FindEntry (TKey键)

System.Collections.Generic.Dictionary的2。get_Item (TKey键)

在Xclna.Xna.Animation.InterpolationController.GetCurrentBoneTransform (BonePose姿势)

我本以为KeyNotFoundException错误的关键,但相反,我得到"索引是在数组的边界之外"。我不明白我怎么能从上面的代码得到异常?

这个代码是单线程的。

字典查找抛出“索引在数组边界之外”

A "索引超出数组边界。"字典(或System.Collections命名空间中的任何东西)上的错误,当文档说错误不应该被抛出时,总是由违反线程安全引起。

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

  • 无限并发读,0写。
  • 0个读取,1个写入。

你必须使用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();
    }
 }

或将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;
 }

UPDATE:我真的不明白你所展示的代码是如何导致这个问题的。下面是导致抛出的函数的源代码。

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;
}

代码抛出ArgumentOutOfRangeException的唯一方式是如果你试图索引bucketsentries中的非法记录。

因为你的键是一个string,字符串是不可变的,我们可以排除一个键在被放入字典后被改变的hashcode值。剩下的就是一个buckets[hashCode % buckets.Length]呼叫和几个entries[i]呼叫。

buckets[hashCode % buckets.Length]失败的唯一可能是buckets的实例在buckets.Length属性调用和this[int index]索引器调用之间被替换。buckets被替换的唯一时间是当ResizeInsert内部调用时,Initialize被构造函数/第一次调用Insert或调用OnDeserialization时调用。

Insert只在this[TKey key]的setter、Add的公共函数和OnDeserialization内部被调用。替换buckets的唯一方法是,在buckets[hashCode % buckets.Length]调用期间,FindEntry调用发生在另一个线程上,同时调用列出的三个函数之一。

我们可以得到一个坏的entries[i]调用的唯一方法是,如果entries被交换出我们(遵循与buckets相同的规则),或者我们得到一个坏的i值。获得i错误值的唯一方法是entries[i].next返回错误值。从entries[i].next中获得错误值的唯一方法是在Insert, ResizeRemove期间进行并发操作。

我唯一能想到的是OnDeserialization调用出现问题,并且在反序列化之前开始有坏数据,或者有更多的AnimationChannelCollection代码影响您没有显示给我们的字典。