【问题标题】:Data structure with key and value as class type and access value using both key and index以键和值作为类类型并使用键和索引访问值的数据结构
【发布时间】:2016-10-11 13:53:03
【问题描述】:

我在 C# 中寻找一种类似于 Dictionary 的数据结构,我可以通过使用索引和键来访问值。该值的类型是 Class 而不是字符串。

我正在创建一个类库,我试图在其中创建一个名为Item 的方法,该方法从我的值和键对数据结构中返回值。但是如果我将整数参数传递给Item,那么它应该通过使用索引来获取值,如果将字符串类型传递给Item,那么它应该通过使用键来访问值(如在字典中)

注意:我提到了this 链接。但是 System.Collections.Specialized.NameValueCollection 不能使用,因为它只能将字符串存储为值,但我希望类类型作为值。我想同时使用键和索引来访问值。

【问题讨论】:

  • Dictionary<TKey, TValue> ? TValue 可以是一个基类。
  • @MaxSorin 对不起。我没有得到你。我想同时使用键和索引来访问值。
  • 如上,Max 是正确的,使用泛型您可以将类作为值存储在字典中,然后针对该字典的 value 列运行 linq 查询以过滤出您需要的结果。跨度>
  • 我想你正在寻找OrderedDictionary

标签: c# list dictionary collections


【解决方案1】:

更新了测试以删除/添加项目到字典。似乎删除然后将新项目添加到字典会导致新项目出现在已删除项目的位置。这让我声称​​使用 Dictionary 这个任务会起作用 false。我更愿意留下这个答案。即使它不是一个有效的解决方案,它也可以说明为什么在想要通过键和索引访问集合时不应使用Dictionary 解决方案。

using System;
using NUnit.Common;
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;

namespace UnitTests
{
    [TestFixture]
    public class UnitTest1
    {
        [Test]
        public void TestMethod1()
        {

            var types = new[] { typeof(int), typeof(float), typeof(Guid), typeof(UnitTest1) };

            Dictionary <Guid, object> dictionary = types.Select(x => Activator.CreateInstance(x)).ToDictionary(y => Guid.NewGuid());

            types.ToList().ForEach(t =>
                Assert.IsTrue(dictionary.Any(x => x.Value.GetType() == t))
            );

            Assert.IsTrue(dictionary.ElementAt(0).Value is int);
            Assert.IsTrue(dictionary.ElementAt(1).Value is float);
            Assert.IsTrue(dictionary.ElementAt(2).Value is Guid);
            Assert.IsTrue(dictionary.ElementAt(3).Value is UnitTest1);

            dictionary.Add(Guid.NewGuid(), Guid.NewGuid());

            Assert.IsTrue(dictionary.ElementAt(4).Value is Guid);

            //Remove the Guid
            dictionary.Remove(dictionary.ElementAt(2).Key);

            //See that the Element at the 2 position is now UnitTest1
            Assert.IsTrue(dictionary.ElementAt(2).Value is UnitTest1);

            //See that the Element at the 1 position is still float
            Assert.IsTrue(dictionary.ElementAt(1).Value is float);

            dictionary.Add(Guid.NewGuid(), new List<Guid>());

            //See that a newly added item is put the end of the dictionary
            Assert.IsTrue(dictionary.ElementAt(4).Value is List<Guid>); //This Assertion Fails. The List<Guid> appears at position 2.
        }
    }    
}

上面的代码创建了一个包含各种类型对象的字典,然后根据类型在字典中找到它们。最后我们在 0 位置获取字典元素并断言它是一个int。请注意,字典的键未使用,并且可能是任何数据类型。

【讨论】:

  • 你不应该以这种方式使用Dictionary&lt;TKey,TValue&gt;,因为枚举检索值的顺序是未定义的。它甚至在documentation 中明确提到:“出于枚举的目的,字典中的每个项目都被视为表示值及其键的 KeyValuePair 结构。返回项目的顺序是未定义的。 "
  • 您会注意到我正在使用Dictionary&lt;&gt; 实现的接口定义的扩展方法。我没有使用 .ToArray()[0] 来获取 0 位置的值。添加到答案中的其他 Assert 语句将证明行为符合预期。虽然这确实会导致通过集合进行额外的重新枚举。
  • 我确实注意到了,但这并没有改变任何东西。没有什么可以让您确信ElementAt(4) 确实是添加到字典中的第五个元素。 .Net 4.5.2 实现(我正在运行的那个)似乎就是这种情况,但这是一种未定义的行为,是您永远不应该依赖的实现细节。
  • 所以在我看来,文档在这一点上令人困惑。其中一篇声明已实现Enumerable 扩展。但是,您的引用表明相反,并且在我的回答中通过单元测试证明是错误的。
  • 另请注意,如果您考虑删除,所有地狱都会崩溃。试试这个:var myDict = new Dictionary&lt;int,int&gt;{{0,0}, {1,1},{2,2}}; myDict.Remove(0); myDict.Add(3,3); var mysteryResult = myDict.ElementAt(0).Value; 不运行,神秘结果的值是多少?
【解决方案2】:

如果非通用集合满足您的需求,您可以使用OrderedDictionary。但是,如果您使用值类型,则需要进行大量装箱和拆箱。

如果您绝对想要通用版本,则必须构建自己的版本。例如:

public class OrderedDictionary<TKey, TValue>
{
    private Dictionary<TKey, TValue> _innerDictionary = new Dictionary<TKey, TValue>();
    private List<TKey> _keys = new List<TKey>();

    public TValue this[TKey key]
    {
        get
        {
            return _innerDictionary[key];
        }

        set
        {
            if(!_innerDictionary.ContainsKey(key))
            {
                _keys.Add(key);
            }
            _innerDictionary[key] = value;
        }
    }

    public TValue this[int index]
    {
        get
        {
            return _innerDictionary[_keys[index]];
        }

        set
        {
            _innerDictionary[_keys[index]] = value;
        }
    }        

    public void Add(TKey key, TValue value)
    {
        _innerDictionary.Add(key, value);
        _keys.Add(key);
    }

    public void Clear()
    {
        _innerDictionary.Clear();
        _keys.Clear();
    }

    public bool Remove(TKey key)
    {
        if (_innerDictionary.Remove(key))
        {
            _keys.Remove(key);
            return true;
        }
        else
        {
            return false;
        }
    }

}

您可能需要更多的异常处理,但大部分功能都在这里。另外,如果你想从那里实现IDictionary&lt;TKey,TVale&gt;,那么缺少的成员应该是微不足道的。

【讨论】:

    【解决方案3】:
        System.Collections.Generic.Dictionary<TKeyItem, TValueItem> myDictionary =
            new System.Collections.Generic.Dictionary<TKeyItem, TValueItem>();
        var key = new TKeyItem();   //create key object
        var val = new TValueItem(); //create value object
        myDictionary.Add(key, val); //add key value pair to the dictionary
        var valueByKey = myDictionary[key];  // it will return val by key
        var valuebyIndex = myDictionary.Values.ElementAt(0); // it will retun val by index
    

    【讨论】:

    • 查看 Max Sorin 回答的评论:永远不要通过索引访问字典的值,顺序未定义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-01
    • 1970-01-01
    • 2014-01-02
    相关资源
    最近更新 更多