通过数字索引访问字典的Keys键

167

我正在使用一个 Dictionary<string, int>,其中的 int 是键的计数。

现在,我需要访问字典中最后插入的键,但我不知道它的名称。显然的尝试:

int LastCount = mydict[mydict.keys[mydict.keys.Count]];

无法使用,因为Dictionary.Keys未实现[]索引器。

我想知道是否有类似的类? 我考虑使用Stack,但它只存储字符串。 我现在可以创建自己的结构,然后使用Stack<MyStruct>,但我想知道是否还有另一种选择,本质上是实现Keys上的[]索引器的Dictionary?


1
如果将该变量装箱会发生什么? - Paul Prewett
15个回答

232

如@Falanwe在评论中指出,像这样做是不正确的:

int LastCount = mydict.Keys.ElementAt(mydict.Count -1);

如果你需要对字典的键进行排序,就不应该依赖于键的顺序。 如果您需要排序,请使用OrderedDictionary,如此答案所建议的那样。 此页面上的其他答案也很有趣。


1
似乎无法与 HashTable 一起使用。'System.Collections.ICollection' 不包含 'ElementAt' 的定义,也找不到接受类型为 'System.Collections.ICollection' 的第一个参数的扩展方法 'ElementAt'。 - v.oddou
23
看到一个如此明显错误的答案被接受并获得那么多赞,让人感到害怕。这是错误的,因为正如Dictionary<TKey,TValue>文档所述,“Dictionary<TKey, TValue>.KeyCollection中键的顺序是未指定的。”由于顺序是未定义的,您无法确定哪个键位于最后一位(mydict.Count -1)。 - Falanwe
1
这很可怕...但对我很有帮助,因为我正在寻找确认我的怀疑,即您不能依赖顺序!!! 感谢@Falanwe - Charlie
3
对于一些人来说,顺序并不重要 - 只要你按照所有的键都试过了就可以了。 - Royi Mindel
@Falanwe 为什么不先对字典进行排序,以便按所需顺序排列? - MGot90
显示剩余2条评论

59
你可以使用一个 OrderedDictionary

它代表了一组可以通过键或索引访问的键值对。


45
19个赞后,没有人提到OrderedDictionary仍然不允许通过索引获取键吗? - Lazlo
1
您可以使用OrderedDictionary使用整数索引访问值,但不能使用System.Collections.Generic.SortedDictionary<TKey, TValue>,其中索引需要是TKey。 - Maxence
OrderedDictionary的名称与该集合函数相关,以维护元素按添加顺序排列。在某些情况下,顺序与排序具有相同的含义,但在此集合中不是这样。 - Sharunas Bielskis

18

字典就是哈希表,所以你无法知道插入顺序!

如果你想知道最后插入的键,我建议扩展字典以包含一个LastKeyInserted值。

例如:

public MyDictionary<K, T> : IDictionary<K, T>
{
    private IDictionary<K, T> _InnerDictionary;

    public K LastInsertedKey { get; set; }

    public MyDictionary()
    {
        _InnerDictionary = new Dictionary<K, T>();
    }

    #region Implementation of IDictionary

    public void Add(KeyValuePair<K, T> item)
    {
        _InnerDictionary.Add(item);
        LastInsertedKey = item.Key;

    }

    public void Add(K key, T value)
    {
        _InnerDictionary.Add(key, value);
        LastInsertedKey = key;
    }

    .... rest of IDictionary methods

    #endregion

}

然而,当您使用.Remove()时,会遇到问题,因此为了克服这个问题,您将不得不保持插入的键的有序列表。


8
为什么不扩展字典类以添加最后插入的关键字属性。可能像以下这样?
public class ExtendedDictionary : Dictionary<string, int>
{
    private int lastKeyInserted = -1;

    public int LastKeyInserted
    {
        get { return lastKeyInserted; }
        set { lastKeyInserted = value; }
    }

    public void AddNew(string s, int i)
    {
        lastKeyInserted = i;

        base.Add(s, i);
    }
}

2
你正在将lastKeyInserted设置为最后插入的值。你可能是想将其设置为最后插入的键,或者你需要更好的变量和属性名称。 - Fantius

6
您总是可以这样做:

string[] temp = new string[mydict.count];
mydict.Keys.CopyTo(temp, 0)
int LastCount = mydict[temp[mydict.count - 1]]

但我不建议这么做。不能保证最后插入的键值一定是在数组末尾。对于 Keys MSDN文档 中没有指定顺序且有可能随时改变。在我的短暂测试中,似乎是按照插入顺序排序的,但如果需要正确的跟踪记录,最好像你建议的那样构建适当的簿记系统,或者如果只需要知道最新的键,则使用单个变量缓存。


5

我认为你可以像这样做,语法可能有误,我有一段时间没有使用C#了。 获取最后一个项:

Dictionary<string, int>.KeyCollection keys = mydict.keys;
string lastKey = keys.Last();

或者使用Max而不是Last来获取最大值,我不知道哪个更适合您的代码。

2
我想补充一点,由于“Last()”是一个扩展方法,您需要.NET Framework 3.5,并在.cs文件的顶部添加“using System.Linq”。 - SuperOli
当使用 Dist<string,string> 时,可以尝试以下代码(显然是用于最后一步的):KeyValuePair<string, string> last = oAuthPairs.Last();如果kvp.Key不等于last.Key,则执行以下操作: _oauth_ParamString = _oauth_ParamString + "&"; - Tim Windsor

4
如果您决定使用可能会出现故障的危险代码,此扩展函数将根据其内部索引(对于Mono和.NET目前似乎与枚举Keys属性获得的顺序相同)从Dictionary<K,V>中提取一个键。
最好使用Linq:dict.Keys.ElementAt(i),但该函数将进行O(N)迭代;以下是O(1),但具有反射性能惩罚。
using System;
using System.Collections.Generic;
using System.Reflection;

public static class Extensions
{
    public static TKey KeyByIndex<TKey,TValue>(this Dictionary<TKey, TValue> dict, int idx)
    {
        Type type = typeof(Dictionary<TKey, TValue>);
        FieldInfo info = type.GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance);
        if (info != null)
        {
            // .NET
            Object element = ((Array)info.GetValue(dict)).GetValue(idx);
            return (TKey)element.GetType().GetField("key", BindingFlags.Public | BindingFlags.Instance).GetValue(element);
        }
        // Mono:
        info = type.GetField("keySlots", BindingFlags.NonPublic | BindingFlags.Instance);
        return (TKey)((Array)info.GetValue(dict)).GetValue(idx);
    }
};

哦,修改以改善答案却遭到了负评。我没有清楚地表明代码显然很丑陋,应该相应地考虑它吗? - Glenn Slayden

4

如果键嵌入值中,则一种替代方案是KeyedCollection

只需创建一个封闭类的基本实现即可使用。

因此,可以替换Dictionary<string, int>(这不是一个很好的例子,因为int没有清晰的键)。

private sealed class IntDictionary : KeyedCollection<string, int>
{
    protected override string GetKeyForItem(int item)
    {
        // The example works better when the value contains the key. It falls down a bit for a dictionary of ints.
        return item.ToString();
    }
}

KeyedCollection<string, int> intCollection = new ClassThatContainsSealedImplementation.IntDictionary();

intCollection.Add(7);

int valueByIndex = intCollection[0];

关于您对密钥的评论,请查看我对此问题的后续回答。 - takrl

4
我同意Patrick回答的第二部分。即使在某些测试中似乎保持插入顺序,文档(以及字典和哈希的正常行为)明确说明排序是未指定的。
依赖键的顺序只会让你麻烦不断。像Patrick说的那样,添加自己的记录(只需一个变量来记录最后添加的键)以确保正确性。此外,不要被所有方法如Last和Max所诱惑,因为它们可能与键比较器有关(我不确定)。

3
您提出的问题让我认为字典中的 int 包含了该项在字典中的“位置”。鉴于关键字不按添加顺序存储的说法,如果这是正确的,那么 keys.Count(或 .Count - 1,如果您使用基于零的计数)仍应始终是最后输入的键的数量?
如果是正确的,您是否有任何理由不能使用 Dictionary<int, string>,以便您可以使用 mydict[mydict.Keys.Count]?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接