3

PROBLEM

I need a method for generating keys for a dictionary of objects. However, I have a few requirements that are making this a bit difficult. Here is the scenario:

  1. The Dictionary is a list of reference type objects.
  2. The Dictionary is private, within a static class.
  3. External code needs to obtain a key to specific objects within the dictionary, but MUST NOT have access to the objects within the dictionary, or the dictionary itself.
  4. Given a specific object within the dictionary, the key must be consistently re-calculable/derivable. If properties on the object change, the key MUST NOT CHANGE.
  5. Conversely, if a new object is created that can evaluate to being equal to another object within the dictionary, the key must be different since they are two, separate objects.
  6. This implementation must be thread safe.

NON-SOLUTIONS

Solution #1
All .Net objects contain a method called .GetHashCode(), which returns an integer value. You can use this as a key.

Problem
Not possible. MSDN States:

Two objects that are equal return hash codes that are equal.

This breaks req #5 and I assume (but not tested) req. #4. I would love to have an option like this, if it could meet these rules.

Solution #2
Convert the pointer to the object to an int and use that as a key.

Problem
This breaks the essence of req. #3. Passing pointers, and using them as keys doesn't feel safe.

Solution #3
Convert the pointer to the object to an integer hash the value and use the hash as a key.

Problem
Although this doesn't break any rules, I'd prefer to avoid accessing pointers since this would involve using unsafe code. I'm not opposed to using unsafe code if I have to, but I'd prefer to avoid it if at all possible.

CONCLUSION

Maybe my requirements are a bit picky. There has to be some reasonable way of deriving a key from a unique object. Has anyone ever experienced such a scenario and solved this dilemma?

RLH
  • 15,230
  • 22
  • 98
  • 182
  • 1
    Isn't 4 contradicting itself, and number 5? – neeKo Nov 07 '13 at 18:58
  • 4
    How about generating guids for each object when adding them to the dictionary and use that as the key? – David Arno Nov 07 '13 at 18:58
  • Solution 3 absolutely will not work. The GC is free to move your object at any time and so you both risk ending up with a hash/ object ptr disconnect (probably not too bad) or another object being placed at your pointer location (potential key clash and a hideous "heisenbug" to fix. – David Arno Nov 07 '13 at 19:01
  • 1
    @DavidArno Guid is in direct conflict with requirement 4: "the key most be consistently re-calculable. – neeKo Nov 07 '13 at 19:36

3 Answers3

8

1 The Dictionary is a list of ByRef objects.

Objects are always 'by reference' in .NET. This may be the beginning of the misunderstanding. Reference equality is what you need/want.

3 External code needs to obtain a key to specific objects within the dictionary, but MUST NOT have access to the objects within the dictionary, or the dictionary itself.

This is the real problem. Without it, a reference to the object itself would have worked. But the framework still provides all of your functionality, right off-the-shelf:

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;
}

And to satisfy req #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];
}

Note: these samples are not thread-safe (req 6), but that's a standard feature to resolve.

H H
  • 263,252
  • 30
  • 330
  • 514
  • That's a really neat solution. +1 – David Arno Nov 07 '13 at 19:02
  • Probably considered pedantic but `Objects are always 'by reference' in .NET` is not true since `System.ValueType` is itself is inherited from `System.Object`. See http://stackoverflow.com/questions/8152761/since-int32-is-a-value-type-why-does-it-inherit-tostring – m.edmondson Nov 07 '13 at 19:05
  • Although to fulfil the 4th requirement you need a second dictionary to may back from values to keys but that should not be difficult to add. – user1937198 Nov 07 '13 at 19:05
  • @m.edmondson But ValueTypes are not objects. – H H Nov 07 '13 at 19:06
  • @user1937198 - Yes, for the derivable part. But that may be superfluous now. Otherwise you will indeed need a 2nd dictionary. – H H Nov 07 '13 at 19:08
  • @HenkHolterman - Surely it is? It's a derivation from System.Object and inherits all it's functionality such as `GetType()` and `ToString()`. – m.edmondson Nov 07 '13 at 19:10
  • Thank you, Henk. Simple, and elegant, but as user1937198 pointed out, that breaks rule #4. Happen to have any thoughts on Tim S. solution? – RLH Nov 07 '13 at 19:14
  • @m.edmondson - this depends on very exact use of terminology. Any valuetype instance IS-A System.Object but they're not objects. – H H Nov 07 '13 at 19:14
  • Rule #4 and #6 are easily implemented, it would have been easier to answer if you had provided code for desired use. I'll add an outline. – H H Nov 07 '13 at 19:15
  • I need to execute batches of SQL commands in transactions. Multiple transactions, for the same db connection, may exist. I need a way of referencing the SqlTransaction object, from outside of my DB processor class, without passing around the transaction object. This is a simplistic explanation of the requirement. – RLH Nov 07 '13 at 19:22
  • FYI, your solution is looking better. This may be the answer I need. – RLH Nov 07 '13 at 19:22
  • @m.edmondson: Given `int foo=34; object bar=foo;`, variable `foo`, of type `System.Int32`, will hold a collection of bits which is *not* an object, but the assignment to `bar` will create a heap object of type `System.Int32` which does derive from `ValueType` and `Object`, and which holds the same data that `foo` held. Basically, there's a universe of variable types and a universe of heap object types; each type definition adds a type into both universes. C# tries to pretend that there's no difference between the types, but the reality is that they are in fact different. – supercat Nov 13 '13 at 00:16
  • @m.edmondson: Storage locations of types other than `System.Enum` which inherit from `ValueType` exhibit value semantics, while heap objects of *all* types, including those derived from `ValueType`, exhibit reference semantics. – supercat Nov 13 '13 at 00:17
  • You probably want to use a [`ConditionalWeakTable`](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2.getvalue?view=netframework-4.8) for `_keyForItem`. That avoids the issue where two distinct objects placed in the dictionary might be equal, creating a collision. Also, it avoids the memory leak if you needed to be able to keep the original key associated with the object even after the value is removed. – binki Feb 10 '20 at 18:50
1

I think that your requirement boils down to that you want to compare and hash by reference equality. Using this as the IEqualityComparer<T> in your dictionary will do that, using RuntimeHelpers.GetHashCode and object.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);
    }
}

Note that since pointers and references aren't the same, your reference shouldn't be usable as a pointer, reducing the risk of unsafe code using this inappropriately.

Community
  • 1
  • 1
Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • Bingo, I think you have the answer. Rather simple too. I'll attempt to implement this and if this works as I hope, you have the answer mark. – RLH Nov 07 '13 at 19:14
-1

How about defining a structure something like:

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;
    }
}

Given a reference to an object, one may construct a BlindIdentityToken which will compare equal to any other BlindIdentityToken constructed for the same object, but will compare unequal to anything else. Because it is a structure type and implements IEquatable<BlindIdentityToken>, constructing a token and looking it up in a dictionary will not require a heap allocation. Because it uses RuntimeHelpers.GetHashCode() it should ignore any override of GetHashCode by the object whose reference is encapsulated, and because o is a private field, it is somewhat protected from outside code sneaking a reference to it.

supercat
  • 77,689
  • 9
  • 166
  • 211