c#Dictionary lookup throws“索引超出了数组的范围”

时间:2016-10-07 18:05:52

标签: 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]; }
        }
}

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

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

     

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

     

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

     

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

我原本期望KeyNotFoundException带有错误的键,但我得到“Index超出了数组的范围”。 我不明白我是如何从上面的代码中获得该异常的?

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

1 个答案:

答案 0 :(得分:10)

“索引超出了数组的范围。错误在词典(或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;
 }

更新:我真的不知道你所展示的代码是如何造成这种情况的。 Here is the source code表示导致它被抛出的函数。

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]的设置器,公共Add函数和OnDeserialization内部。 buckets被替换的唯一方法是,如果我们在FindEntry调用期间在另一个线程上发生buckets[hashCode % buckets.Length]调用的同时调用三个列出的函数之一。

我们唯一可以获得错误entries[i]来电的方法是entries是否被我们换出(遵循与buckets相同的规则)或者我们得到{{{ 1}}。获取i错误值的唯一方法是i返回错误值。从entries[i].next获取错误值的唯一方法是在entries[i].nextInsertResize期间进行并发操作。

我唯一能想到的是Remove调用出错了你在反序列化之前有错误的数据开始,或者OnDeserialization有更多代码影响字典你没有向我们展示。