为对象派生唯一字典键的最佳方法是什么



>PROBLEM

我需要一种方法来为对象字典生成键。 但是,我有一些要求使这有点困难。 场景如下:

  1. 字典是引用类型对象的列表。
  2. 字典private,在静态类中。
  3. 外部代码需要获取字典中特定对象的键,但不得访问字典中的对象或字典本身。
  4. 给定字典中的特定对象,键必须始终是可重新计算/可派生的。 如果对象的属性发生更改,则键不得更改。
  5. 相反,如果创建了一个新对象,该对象可以计算为equal字典中的另一个对象,则键必须不同,因为它们是两个独立的对象。
  6. 此实现必须是线程安全的。

非解决方案

解决方案#1
所有 .Net 对象都包含一个名为 .GetHashCode() 的方法,该方法返回一个整数值。 您可以将其用作密钥。

问题
不可能。 MSDN 声明:

两个相等

的对象返回相等的哈希代码。

这打破了要求#5,我假设(但未测试)要求#4。 我很想有这样的选择,如果它能满足这些规则的话。

解决方案#2
将指向对象的指针转换为int并将其用作键。

问题
这打破了要求#3的本质。 传递指针并将它们用作键感觉不安全。

解决方案#3
将指向对象的指针转换为整数哈希,并将哈希用作键。

问题
虽然这不会违反任何规则,但我宁愿避免访问指针,因为这将涉及使用unsafe代码。 如果有必要,我不反对使用不安全的代码,但如果可能的话,我宁愿避免它。

结论

也许我的要求有点挑剔。 必须有一些合理的方法来从唯一对象派生密钥。 有没有人经历过这样的场景并解决了这个困境?

1 字典是 ByRef 对象的列表。

对象在 .NET 中始终是"通过引用"的。这可能是误会的开始。参考相等是您需要/想要的。

3 外部代码需要获取字典中特定对象的键,但不得访问字典中的对象或字典本身。

这才是真正的问题。没有它,对对象本身的引用将起作用。但是该框架仍然提供所有现成的功能:

private Dictionary<object, MyClass> _myStore;
// add an item and return a key    
public object Add(MyClass item)
{
    object key = new object();
    _myStore.Add(key, item);
    return key;
}

为了满足要求#4:

private Dictionary<object, MyClass> _itemForKey;    // was _myStore
private Dictionary<MyClass, object> _keyForItem;

// add an item and return a key    
public object Add(MyClass item)
{
    object key = new object();
    _itemForKey.Add(key, item);
    _keyForItem.Add(item, key);
    return key;
}
protected object DeriveKeyFromItem(MyClass item)
{
   return _keyForItem[item];
}

注意:这些示例不是线程安全的(req 6),但这是要解决的标准功能。

我认为您的要求归结为您要通过引用相等性进行比较和哈希。使用它作为字典中的IEqualityComparer<T>就可以做到这一点,使用 RuntimeHelpers.GetHashCodeobject.ReferenceEquals .

public class ReferenceEqualityComparer<T> : IEqualityComparer<T>
{
    public bool Equals(T x, T y)
    {
        return object.ReferenceEquals(x, y);
    }
    public int GetHashCode(T obj)
    {
        return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
    }
}

请注意,由于指针和引用不同,因此引用不应用作指针,从而降低unsafe代码不当使用它的风险。

如何定义一个结构,如下所示:

public struct BlindIdentityToken : IEquatable<BlindIdentityToken>
{
    Object o;
    public BlindIdentityToken(Object obj)
    {
        o = obj;
    }
    public override int GetHashCode()
    {
        return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(o);
    }
    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != typeof(BlindIdentityToken))
            return false;
        return ((BlindIdentityToken)obj).o == o;
    }
    public bool Equals(BlindIdentityToken other)
    {
        return o == other.o;
    }
}

给定对对象的引用,可以构造一个BlindIdentityToken,该将比较为同一对象构造的任何其他BlindIdentityToken,但与其他任何对象进行比较不相等。 因为它是一个结构类型并实现IEquatable<BlindIdentityToken>,构造一个令牌并在字典中查找它不需要堆分配。 因为它使用RuntimeHelpers.GetHashCode()所以它应该忽略封装了引用的对象对GetHashCode的任何覆盖,并且由于o是一个私有字段,所以它在某种程度上受到保护,防止外部代码偷偷引用它。

相关内容

最新更新