如何根据具有相同哈希码的对象获取字典项?

3

考虑以下私有成员:

private ConcurrentDictionary<CollectionInfo, ServiceInfo> _collectionsServicesMapping;

CollectionInfo类覆盖并添加了一些附加属性:

class CollectionInfo
{
    public Guid InstanceId { get; set; }

    public string CollectionName { get; set; }

    public string WorkFlowName { get; set; }

    public Guid DomainId { get; set; }

    public override bool Equals(object obj)
    {
        return obj is CollectionInfo && (obj as CollectionInfo).InstanceId.Equals(InstanceId);
    }

    public override int GetHashCode()
    {
        return InstanceId.GetHashCode();
    }
}

在我需要的情境下,我正在通过InstanceId查找CollectionInfo:
private IRequestHandler GetServiceByInstanceId(Guid instanceId)
{
}

我看到两个可选方案:

_collectionsServicesMapping.TryGetValue(new CollectionInfo() { InstanceId = instanceId }, out si)

_collectionsServicesMapping.FirstOrDefault(x => x.Key.InstanceId.Equals(instanceId));

但这迫使我要么创建一个冗余的虚假实例,要么扫描整个字典。是否有一种更有效的方法来基于具有相同哈希码的另一个对象获取字典项?

为什么不使用 ConcurrentDictionary<Guid, ServiceInfo> 呢?没有必要将 CollectionInfo 作为键。 - Magnus
5
为什么字典映射首先要从集合信息开始,如果实际上它只是在映射它们的ID?直接根据ID创建一个映射即可。 - Eric Lippert
这是一个可行的选择,也是我开始使用的。在这种情况下,我需要同时使用CollectionInfoServiceInfo。只是想知道是否有一种方法可以保存额外的字典并管理它们之间的并发性。 - Yosi Dahari
1
我认为你需要审查所有使用集合的用例(包括如何更新和查询),以确定最适合的结构。有很多可能性,你最好创建自己的集合类来简化接口,并允许你更改底层实现而不破坏使用它的代码。 - Jack A.
我同意 @JackA 的看法。但是,当找到第一个匹配项时,FirstOrDefault 不会继续全面扫描吗? - xum59
@xum59 - 这是平均情况下的O(n)操作,对于从数据结构中获取一个项目来说非常昂贵。 - Yosi Dahari
2个回答

1
我认为你在这里并没有真正的问题。但是让我们来仔细分析一下(请见结尾以了解我的建议)。
创建一个冗余的虚拟实例。
创建新实例是相对廉价的操作。如果代码比直接使用 GUID 稍微丑陋,那么你还有很多选项。
//excention method (in some static class)
public static ServiceInfo GetServiceByGuid (
   this ConcurrentDictionary<CollectionInfo, ServiceInfo> dic, Guid id){
   ServiceInfo si;
   dic.TryGetValue(new CollectionInfo() { InstanceId = id}, out si);
   return si;
}

或者

//implicit coversion operator (in CollectionInfo)
public static implicit operator CollectionInfo(Guid id){
    return new CollectionInfo(new CollectionInfo() { InstanceId = id};
}

然后,您只需将Guid“instanceId”传递给字典的“TryGetValue”方法即可。
扫描整个字典没有任何理由。您实际上仅扫描键而不是“整个”字典,但“TryGetValue”将更具性能,因为它可以利用哈希来快速查找您要查找的项。
更改字典后,我认为您想要以下内容:
ConcurrentDictionary > 这样,您仍然可以获得并发性,可以基于guid(id)匹配CollectionInfo / ServiceInfo,并且无需处理重载(CollectionInfo中的GetHashCode())。
private IRequestHandler GetServiceByInstanceId(Guid instanceId)
{
   Tuple<CollectionInfo,ServiceInfo> pair;
   if (_collectionsServicesMapping.TryGetValue(instanceId, out pair))
   {
      return pair.Item2;
   }

   // whatever you want to return if instanceId wasn't found
   return null;
}

1
我认为你回答的最后一部分是应该被强调的。InstanceId 应该作为键,而 CollectionInfoServiceInfo 需要组合成值。 - sstan

1

有没有一种更高效的方法,根据具有相同哈希码的对象获取字典项?

很遗憾,没有。与Philip Pittle的答案相反,我认为你(以及其他处于类似情况的人)确实存在问题。我们是“封装过度”的受害者,这始于Dictionary<TKey, TValue>并延续至ConcurentDictionary<TKey, TValue>。这两个类都可以轻松地公开一个类似于的方法:

IEnumerable<KeyValuePair<TKey, TValue>> GetItems(int hashCode) 

或者

bool TryGetValue(int hashCode, Func<TKey, bool> predicate, out TValue value)

但是它们不行。不幸的是,类实现外不能模拟类似的东西。
所以你只能使用提到的解决方法。我会选择使用“虚假实例”方法 - 至少有时候可以这样做(如果类需要复杂的构造函数,并且强制执行强验证,不允许虚假实例化)。并等待微软开源BCL :-)
附言:创建一个不同的字典来存储Guid,为什么要保留2个副本的Guid(16字节值类型),如果它已经包含在类的实例中?

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