如果值是对象并且这些对象的属性是键,是不是有比 Dictionary 更好的数据结构?

Posted

技术标签:

【中文标题】如果值是对象并且这些对象的属性是键,是不是有比 Dictionary 更好的数据结构?【英文标题】:Is there a better data structure than Dictionary if the values are objects and a property of those objects are the keys?如果值是对象并且这些对象的属性是键,是否有比 Dictionary 更好的数据结构? 【发布时间】:2011-01-10 09:35:59 【问题描述】:

我有一个Dictionary<int, object>,其中intobj 的一个属性。有没有更好的数据结构呢?我觉得使用属性作为键是多余的。

Dictionary<int, obj> 是容器类中的一个字段,允许基于int id 编号随机索引到obj 值。容器类中的简化(无异常处理)索引器如下所示:

obj this[int id]

     get return this.myDictionary[id];

其中myDictionary 是前面提到的持有对象的Dictionary<int, obj>

这可能是快速随机访问的典型方式,但我想获得第二意见。

【问题讨论】:

Is there a dictionary like collection that can use a property of its value as the key? 的可能重复项 【参考方案1】:

如果出厂设置带来的额外开销不值得,您可以轻松实现自己的KeyedCollectionSystem.Collections.ObjectModel 中的原始 KeyedCollection 在内部是 Dictionary<TKey, TItem>List<TItem>,这意味着您可以在 IList<>IDictionary<> 上定义操作。例如,您可以插入、按索引访问、按插入顺序遍历集合(所有这些都是IList<> 促进的),同时您可以基于键进行快速查找(借助字典)。这意味着当您添加或删除一个项目时,它们必须在两个基础集合上执行,除了用于保存额外 List<> 的小内存开销(但对象不会因此而重复)。虽然加法速度没有太大影响(List<> 加法是 O(1)),removal speed is affected a little.

如果您不关心插入顺序和按索引访问:

public class KeyedCollection<TKey, TItem> : ICollection<TItem>

    MemberInfo _keyInfo;
    Func<TItem, TKey> _keySelector;
    Dictionary<TKey, TItem> _dict;

    public TItem this[TKey key]
    
        get  return _dict[key]; 
    

    public int Count
    
        get  return _dict.Count; 
    

    public bool IsReadOnly
    
        get  return false; 
    

    public ICollection<TKey> Keys
    
        get  return _dict.Keys; 
    

    private ICollection<TItem> Items
    
        get  return _dict.Values; 
    

    public KeyedCollection(Expression<Func<TItem, TKey>> keySelector, IEqualityComparer<TKey> comparer = null)
    
        var keyExpression = keySelector.Body as MemberExpression;
        if (keyExpression != null)
            _keyInfo = keyExpression.Member;

        _keySelector = keySelector.Compile();
        _dict = new Dictionary<TKey, TItem>(comparer);
    



    private TKey GetKeyForItem(TItem item)
    
        return _keySelector(item);
    

    public bool ContainsKey(TKey key)
    
        return _dict.ContainsKey(key);
    

    public bool Contains(TItem item)
    
        return ContainsKey(GetKeyForItem(item));
    

    public bool TryGetItem(TKey key, out TItem item)
    
        return _dict.TryGetValue(key, out item);
    

    public void Add(TItem item)
    
        _dict.Add(GetKeyForItem(item), item);
    

    public void AddOrUpdate(TItem item)
    
        _dict[GetKeyForItem(item)] = item;
    

    public bool UpdateKey(TKey oldKey, TKey newKey)
    
        TItem oldItem;
        if (_keyInfo == null || !TryGetItem(oldKey, out oldItem) || !SetItem(oldItem, newKey))   // important
            return false;

        RemoveKey(oldKey);
        Add(oldItem);
        return true;
    

    private bool SetItem(TItem item, TKey key)
    
        var propertyInfo = _keyInfo as PropertyInfo;
        if (propertyInfo != null)
        
            if (!propertyInfo.CanWrite)
                return false;

            propertyInfo.SetValue(item, key, null);
            return true;
        

        var fieldInfo = _keyInfo as FieldInfo;
        if (fieldInfo != null)
        
            if (fieldInfo.IsInitOnly)
                return false;

            fieldInfo.SetValue(item, key);
            return true;
        

        return false;
    

    public bool RemoveKey(TKey key)
    
        return _dict.Remove(key);
    

    public bool Remove(TItem item)
    
        return RemoveKey(GetKeyForItem(item));
    

    public void Clear()
    
        _dict.Clear();
    

    public void CopyTo(TItem[] array, int arrayIndex)
    
        Items.CopyTo(array, arrayIndex);
    

    public IEnumerator<TItem> GetEnumerator()
    
        return Items.GetEnumerator();
    

    IEnumerator IEnumerable.GetEnumerator()
    
        return GetEnumerator();
    

我已经实现了ICollection&lt;TItem&gt; 以使其更符合标准 - 而且您还可以获得漂亮的集合初始化器语法! :)

示例用法:

var p1 = new Person  Name = "a" ;
var p2 = new Person  Name = "b" ;

var people = new KeyedCollection<string, Person>(p => p.Name)  p1, p2 ;
// p1 == people["a"];
// p2 == people["b"];

【讨论】:

【参考方案2】:

框架中没有执行此操作的具体类。不过有一个抽象的,KeyedCollection。您必须从该类派生您自己的类并实现 GetKeyForItem() 方法。这很简单,只需返回要索引的属性的值即可。

这就是您需要做的所有事情,但请注意 ChangeItemKey()。当您用作键的属性更改值时,您必须做一些有意义的事情。如果您确保该属性是不可变的(只有一个吸气剂),那就很容易了。但是当你不这样做时会很尴尬,对象本身现在需要意识到它被存储在你的集合中。如果您不对其进行任何操作(调用 ChangeItemKey),则该对象会在集合中丢失,您将无法找回它。非常接近泄漏。

注意 Dictionary 如何通过分别指定键值和对象来回避这个问题。您可能仍然无法找回该对象,但至少它不会因设计而丢失。

【讨论】:

很好的建议。正如我向@Lee 提到的那样,这种方法具有暴露一堆其他方法的不良副作用,我不希望我的班级实施。其中最糟糕的是Clear 方法。在这种情况下,我需要将 KeyedCollection 设为只读。 根据您的需要,您可以覆盖例如ClearItems 干脆什么都不做(甚至扔掉)。 @Brian 我认为 Lookup 可能是您想要的,因为我相信您一旦创建就无法修改它。我不确定它是否强制执行密钥的唯一性。【参考方案3】:

C# dynamic properties 的帖子似乎表明使用字典是一种流行的选择。其他帖子建议使用HashTable

Dictionary vs Hashtable

【讨论】:

【参考方案4】:

有一个KeyedCollection 类。

编辑:KeyedCollection 可以在内部使用字典,但对于这种特定场景,它比原始字典更清晰的界面,因为您可以直接按值查找。诚然,我觉得它总体上不是很有用。

【讨论】:

很好的建议。但是,如果我让我的容器暗示 KeyedCollection&lt;int, obj&gt; 它会暴露大量我不想暴露的成员。特别是Clear 方法。

以上是关于如果值是对象并且这些对象的属性是键,是不是有比 Dictionary 更好的数据结构?的主要内容,如果未能解决你的问题,请参考以下文章

vue数据绑定原理

8. Vue - v-bind 绑定 Style 对象语法 和 数组语法

C# new 后 得到的却是一个空的对象

几种对象遍历的方法

js判断对象中是不是有某个key

Python3.x基础学习-类--类属性和实例属性