I used to use the apache hashcode builder a lot
Does this exist for C#
This is my homemade builder.
Usage:
hash = new HashCodeBuilder().
Add(a).
Add(b).
Add(c).
Add(d).
GetHashCode();
It does not matter what type fields a,b,c and d are, easy to extend, no need to create array.
Source:
public sealed class HashCodeBuilder
{
private int hash = 17;
public HashCodeBuilder Add(int value)
{
unchecked
{
hash = hash * 31 + value; //see Effective Java for reasoning
// can be any prime but hash * 31 can be opimised by VM to hash << 5 - hash
}
return this;
}
public HashCodeBuilder Add(object value)
{
return Add(value != null ? value.GetHashCode() : 0);
}
public HashCodeBuilder Add(float value)
{
return Add(value.GetHashCode());
}
public HashCodeBuilder Add(double value)
{
return Add(value.GetHashCode());
}
public override int GetHashCode()
{
return hash;
}
}
Sample usage:
public sealed class Point
{
private readonly int _x;
private readonly int _y;
private readonly int _hash;
public Point(int x, int y)
{
_x = x;
_y = y;
_hash = new HashCodeBuilder().
Add(_x).
Add(_y).
GetHashCode();
}
public int X
{
get { return _x; }
}
public int Y
{
get { return _y; }
}
public override bool Equals(object obj)
{
return Equals(obj as Point);
}
public bool Equals(Point other)
{
if (other == null) return false;
return (other._x == _x) && (other._y == _y);
}
public override int GetHashCode()
{
return _hash;
}
}
I use the following:
public static int ComputeHashFrom(params object[] obj) {
ulong res = 0;
for(uint i=0;i<obj.Length;i++) {
object val = obj[i];
res += val == null ? i : (ulong)val.GetHashCode() * (1 + 2 * i);
}
return (int)(uint)(res ^ (res >> 32));
}
Using such a helper is quick, easy and reliable, but it has potential two downsides (which you aren't likely to encounter frequently, but are good to be aware of):
It can generate poor hashcodes for some distributions of params. For instance, for any int x, ComputeHashFrom(x*-3, x) == 0 - so if your objects have certain pathological properties you may get many hash code collisions resulting in poorly performing Dictionaries and HashSets. It's not likely to happen, but a type-aware hash code computation can avoid such problems more easily.
The computation of the hashcode is slower than a specialized computation could be. In particular, it involved the allocation of the params array and a loop - which quite a bit of unnecessary overhead if you've just got two members to process.
Neither of the drawbacks causes any errors merely inefficiency; and both with show up in a profiler as blips in either this method or in the internals of the hash-code consumer.
C# doesn't have a built-in HashCode builder, but you can roll your own. I recently had this precise problem and created this hashcode generator that doesn't use boxing, by using generics, and implements a modified FNV algorithm for generating the specific hash. But you could use any algorithm you'd like, like one of those in System.Security.Cryptography.
public static int GetHashCode<T>(params T[] args)
{
return args.GetArrayHashCode();
}
public static int GetArrayHashCode<T>(this T[] objects)
{
int[] data = new int[objects.Length];
for (int i = 0; i < objects.Length; i++)
{
T obj = objects[i];
data[i] = obj == null ? 1 : obj.GetHashCode();
}
return GetFnvHash(data);
}
private static int GetFnvHash(int[] data)
{
unchecked
{
const int p = 16777619;
long hash = 2166136261;
for (int i = 0; i < data.Length; i++)
{
hash = (hash ^ data[i]) * p;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return (int)hash;
}
}
Microsoft recently released a class to compute hashcodes. Please see https://learn.microsoft.com/en-us/dotnet/api/system.hashcode. You need to include NuGet package Microsoft.Bcl.HashCode in your project to use it.
Usage example:
using System.Collections.Generic;
public class MyClass {
public int MyVar { get; }
public string AnotherVar { get; }
public object MoreVars;
public override int GetHashCode()
=> HashCode.Combine(MyVar, AnotherVar, MoreVars);
}
Nowadays I leverage ValueTuples, ref Tuples or anonymous types:
var hash = (1, "seven").GetHashCode();
var hash2 = Tuple.Create(1, "seven").GetHashCode();
var hash3 = new { Number = 1, String = "seven" }.GetHashCode();
I believe value tuples will be fastest.
Related
I'm looking at how build the best HashCode for a class and I see some algorithms. I saw this one : Hash Code implementation, seems to be that .NET classes HashCode methods are similar (see by reflecting the code).
So question is, why don't create the above static class in order to build a HashCode automatically, just by passing fields we consider as a "key".
// Old version, see edit
public static class HashCodeBuilder
{
public static int Hash(params object[] keys)
{
if (object.ReferenceEquals(keys, null))
{
return 0;
}
int num = 42;
checked
{
for (int i = 0, length = keys.Length; i < length; i++)
{
num += 37;
if (object.ReferenceEquals(keys[i], null))
{ }
else if (keys[i].GetType().IsArray)
{
foreach (var item in (IEnumerable)keys[i])
{
num += Hash(item);
}
}
else
{
num += keys[i].GetHashCode();
}
}
}
return num;
}
}
And use it as like this :
// Old version, see edit
public sealed class A : IEquatable<A>
{
public A()
{ }
public string Key1 { get; set; }
public string Key2 { get; set; }
public string Value { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as A);
}
public bool Equals(A other)
{
if(object.ReferenceEquals(other, null))
? false
: Key1 == other.Key1 && Key2 == other.Key2;
}
public override int GetHashCode()
{
return HashCodeBuilder.Hash(Key1, Key2);
}
}
Will be much simpler that always is own method, no? I'm missing something?
EDIT
According all remarks, I got the following code :
public static class HashCodeBuilder
{
public static int Hash(params object[] args)
{
if (args == null)
{
return 0;
}
int num = 42;
unchecked
{
foreach(var item in args)
{
if (ReferenceEquals(item, null))
{ }
else if (item.GetType().IsArray)
{
foreach (var subItem in (IEnumerable)item)
{
num = num * 37 + Hash(subItem);
}
}
else
{
num = num * 37 + item.GetHashCode();
}
}
}
return num;
}
}
public sealed class A : IEquatable<A>
{
public A()
{ }
public string Key1 { get; set; }
public string Key2 { get; set; }
public string Value { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as A);
}
public bool Equals(A other)
{
if(ReferenceEquals(other, null))
{
return false;
}
else if(ReferenceEquals(this, other))
{
return true;
}
return Key1 == other.Key1
&& Key2 == other.Key2;
}
public override int GetHashCode()
{
return HashCodeBuilder.Hash(Key1, Key2);
}
}
Your Equals method is broken - it's assuming that two objects with the same hash code are necessarily equal. That's simply not the case.
Your hash code method looked okay at a quick glance, but could actually do some with some work - see below. It means boxing any value type values and creating an array any time you call it, but other than that it's okay (as SLaks pointed out, there are some issues around the collection handling). You might want to consider writing some generic overloads which would avoid those performance penalties for common cases (1, 2, 3 or 4 arguments, perhaps). You might also want to use a foreach loop instead of a plain for loop, just to be idiomatic.
You could do the same sort of thing for equality, but it would be slightly harder and messier.
EDIT: For the hash code itself, you're only ever adding values. I suspect you were trying to do this sort of thing:
int hash = 17;
hash = hash * 31 + firstValue.GetHashCode();
hash = hash * 31 + secondValue.GetHashCode();
hash = hash * 31 + thirdValue.GetHashCode();
return hash;
But that multiplies the hash by 31, it doesn't add 31. Currently your hash code will always return the same for the same values, whether or not they're in the same order, which isn't ideal.
EDIT: It seems there's some confusion over what hash codes are used for. I suggest that anyone who isn't sure reads the documentation for Object.GetHashCode and then Eric Lippert's blog post about hashing and equality.
This is what I'm using:
public static class ObjectExtensions
{
/// <summary>
/// Simplifies correctly calculating hash codes based upon
/// Jon Skeet's answer here
/// http://stackoverflow.com/a/263416
/// </summary>
/// <param name="obj"></param>
/// <param name="memberThunks">Thunks that return all the members upon which
/// the hash code should depend.</param>
/// <returns></returns>
public static int CalculateHashCode(this object obj, params Func<object>[] memberThunks)
{
// Overflow is okay; just wrap around
unchecked
{
int hash = 5;
foreach (var member in memberThunks)
hash = hash * 29 + member().GetHashCode();
return hash;
}
}
}
Example usage:
public class Exhibit
{
public virtual Document Document { get; set; }
public virtual ExhibitType ExhibitType { get; set; }
#region System.Object
public override bool Equals(object obj)
{
return Equals(obj as Exhibit);
}
public bool Equals(Exhibit other)
{
return other != null &&
Document.Equals(other.Document) &&
ExhibitType.Equals(other.ExhibitType);
}
public override int GetHashCode()
{
return this.CalculateHashCode(
() => Document,
() => ExhibitType);
}
#endregion
}
Instead of calling keys[i].GetType().IsArray, you should try to cast it to IEnumerable (using the as keyword).
You can fix the Equals method without repeating the field list by registering a static list of fields, like I do here using a collection of delegates.
This also avoids the array allocation per-call.
Note, however, that my code doesn't handle collection properties.
I have a custom class that I was trying to use as a key for a dictionary:
// I tried setting more than enough capacity also...
var dict = new Dictionary<MyPoint, MyPoint>(capacity);
Now let me be clear, the goal here is to compare two SIMILAR but DIFFERENT lists, using X, Y, and Date as a composite key. The values will vary between these two lists, and I'm trying to quickly compare them and compute their differences.
Here is the class code:
public class MyPoint : IEquatable<MyPoint>
{
public short X { get; set; }
public short Y { get; set; }
public DateTime Date { get; set; }
public double MyValue { get; set; }
public override bool Equals(object obj)
{
return base.Equals(obj as MyPoint);
}
public bool Equals(MyPoint other)
{
if (other == null)
{
return false;
}
return (Date == other.Date)
&& (X == other.X)
&& (Y == other.Y);
}
public override int GetHashCode()
{
return Date.GetHashCode()
| X.GetHashCode()
| Y.GetHashCode();
}
}
I also tried keying with a struct:
public struct MyPointKey
{
public short X;
public short Y;
public DateTime Date;
// The value is not on these, because the struct is only used as key
}
In both cases dictionary writing was very, very slow (reading was quick).
I changed the key to a string, with the format:
var dict = new Dictionary<string, MyPoint>(capacity);
var key = string.Format("{0}_{1}", item.X, item.Y);
I was amazed at how much quicker this is -- it's at least 10 times faster. I tried Release mode, no debugger, and every scenario I could think of.
This dictionary will contain 350,000 or more items, so performance does matter.
Any thoughts or suggestions? Thanks!
Another edit...
I'm trying to compare two lists of things in the fastest way I can. This is what I'm working with. The Dictionary is important for fast lookups against the source list.
IList<MyThing> sourceList;
IDictionary<MyThing, MyThing> comparisonDict;
Parallel.ForEach(sourceList,
sourceItem =>
{
double compareValue = 0;
MyThing compareMatch = null;
if (comparisonDict.TryGetValue(sourceItem, out compareMatch))
{
compareValue = compareMatch.MyValue;
}
// Do a delta check on the item
double difference = sourceItem.MyValue- compareValue;
if (Math.Abs(difference) > 1)
{
// Record the difference...
}
});
As others have said in the comments, the problem is in your GetHashCode() implementation. Taking your code, and running 10,000,000 iterations with the string key took 11-12 seconds. Running with your existing hashCode I stopped it after over a minute. Using the following hashCode implementation took under 5 seconds.
public override int GetHashCode()
{
var hashCode = Date.GetHashCode();
hashCode = (hashCode * 37) ^ X.GetHashCode();
hashCode = (hashCode * 37) ^ Y.GetHashCode();
return hashCode;
}
The problem is that when you get into large numbers, the items are all colliding in the same buckets, due to the ORs. A dictionary where everything is in the same bucket is just a list.
If I got you right, you like to use a set while still maintaining the order of the keys. In this case, take SortedSet`1 instead.
Code:
class Program {
static void Main(string[] args) {
SortedSet<MyKey> list = new SortedSet<MyKey>() {
new MyKey(0, 0, new DateTime(2015, 6, 4)),
new MyKey(0, 1, new DateTime(2015, 6, 3)),
new MyKey(1, 1, new DateTime(2015, 6, 3)),
new MyKey(0, 0, new DateTime(2015, 6, 3)),
new MyKey(1, 0, new DateTime(2015, 6, 3)),
};
foreach(var entry in list) {
Console.WriteLine(string.Join(", ", entry.X, entry.Y, entry.Date));
}
Console.ReadKey();
}
}
I changed your MyPoint class as follows:
public sealed class MyKey : IEquatable<MyKey>, IComparable<MyKey> {
public readonly short X;
public readonly short Y;
public readonly DateTime Date;
public MyKey(short x, short y, DateTime date) {
this.X = x;
this.Y = y;
this.Date = date;
}
public override bool Equals(object that) {
return this.Equals(that as MyKey);
}
public bool Equals(MyKey that) {
if(that == null) {
return false;
}
return this.Date == that.Date
&& this.X == that.X
&& this.Y == that.Y;
}
public static bool operator ==(MyKey lhs, MyKey rhs) {
return lhs != null ? lhs.Equals(rhs) : rhs == null;
}
public static bool operator !=(MyKey lhs, MyKey rhs) {
return lhs != null ? !lhs.Equals(rhs) : rhs != null;
}
public override int GetHashCode() {
int result;
unchecked {
result = (int)X;
result = 31 * result + (int)Y;
result = 31 * result + Date.GetHashCode();
}
return result;
}
public int CompareTo(MyKey that) {
int result = this.X.CompareTo(that.X);
if(result != 0) {
return result;
}
result = this.Y.CompareTo(that.Y);
if(result != 0) {
return result;
}
result = this.Date.CompareTo(that.Date);
return result;
}
}
Output:
0, 0, 03.06.2015 00:00:00
0, 0, 04.06.2015 00:00:00
0, 1, 03.06.2015 00:00:00
1, 0, 03.06.2015 00:00:00
1, 1, 03.06.2015 00:00:00
I'm working on implementing GetHashCode() based on the HashCode struct in this answer here. Since my Equals method will consider collections using Enumerable.SequenceEqual(), I need to include the collections in my GetHashCode() implementation.
As a starting point, I'm using Jon Skeet's embedded GetHashCode() implementation to test the output of the HashCode struct implementation. This works as expected using the following test below -
private class MyObjectEmbeddedGetHashCode
{
public int x;
public string y;
public DateTimeOffset z;
public List<string> collection;
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + x.GetHashCode();
hash = hash * 31 + y.GetHashCode();
hash = hash * 31 + z.GetHashCode();
return hash;
}
}
}
private class MyObjectUsingHashCodeStruct
{
public int x;
public string y;
public DateTimeOffset z;
public List<string> collection;
public override int GetHashCode()
{
return HashCode.Start
.Hash(x)
.Hash(y)
.Hash(z);
}
}
[Test]
public void GetHashCode_CollectionExcluded()
{
DateTimeOffset now = DateTimeOffset.Now;
MyObjectEmbeddedGetHashCode a = new MyObjectEmbeddedGetHashCode()
{
x = 1,
y = "Fizz",
z = now,
collection = new List<string>()
{
"Foo",
"Bar",
"Baz"
}
};
MyObjectUsingHashCodeStruct b = new MyObjectUsingHashCodeStruct()
{
x = 1,
y = "Fizz",
z = now,
collection = new List<string>()
{
"Foo",
"Bar",
"Baz"
}
};
Console.WriteLine("MyObject::GetHashCode(): {0}", a.GetHashCode());
Console.WriteLine("MyObjectEx::GetHashCode(): {0}", b.GetHashCode());
Assert.AreEqual(a.GetHashCode(), b.GetHashCode());
}
The next step is to consider the collection in the GetHashCode() calculation. This requires a small addition to the GetHashCode() implementation in MyObjectEmbeddedGetHashCode.
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + x.GetHashCode();
hash = hash * 31 + y.GetHashCode();
hash = hash * 31 + z.GetHashCode();
int collectionHash = 17;
foreach (var item in collection)
{
collectionHash = collectionHash * 31 + item.GetHashCode();
}
hash = hash * 31 + collectionHash;
return hash;
}
}
However, this is a little bit more difficult in the HashCode struct. In this example, when a collection of type List is passed into the Hash method, T is List so trying to cast obj to ICollection or IEnumberable doesn't work. I can successfully cast to IEnumerable, but it causes boxing and I found I have to worry about excluding types like string that implement IEnumerable.
Is there a way to reliably cast obj to ICollection or IEnumerable in this scenario?
public struct HashCode
{
private readonly int hashCode;
public HashCode(int hashCode)
{
this.hashCode = hashCode;
}
public static HashCode Start
{
get { return new HashCode(17); }
}
public static implicit operator int(HashCode hashCode)
{
return hashCode.GetHashCode();
}
public HashCode Hash<T>(T obj)
{
// I am able to detect if obj implements one of the lower level
// collection interfaces. However, I am not able to cast obj to
// one of them since T in this case is defined as List<string>,
// so using as to cast obj to ICollection<T> or IEnumberable<T>
// doesn't work.
var isGenericICollection = obj.GetType().GetInterfaces().Any(
x => x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(ICollection<>));
var c = EqualityComparer<T>.Default;
// This works but using IEnumerable causes boxing.
// var h = c.Equals(obj, default(T)) ? 0 : ( !(obj is string) && (obj is IEnumerable) ? GetCollectionHashCode(obj as IEnumerable) : obj.GetHashCode());
var h = c.Equals(obj, default(T)) ? 0 : obj.GetHashCode();
unchecked { h += this.hashCode * 31; }
return new HashCode(h);
}
public override int GetHashCode()
{
return this.hashCode;
}
}
You can address the collection issue in a couple of ways:
Use a non-generic interface, e.g. ICollection or IEnumerable.
Add an overload for the Hash() method, e.g. Hash<T>(IEnumerable<T> list) { ... }
That said, IMHO it would be better to just leave the struct HashCode alone and put the collection-specific code in your actual GetHashCode() method. E.g.:
public override int GetHashCode()
{
HashCode hash = HashCode.Start
.Hash(x)
.Hash(y)
.Hash(z);
foreach (var item in collection)
{
hash = hash.Hash(item);
}
return hash;
}
If you do want a full-featured version of the struct HashCode type, it looks to me as though that same page you referenced has one: https://stackoverflow.com/a/2575444/3538012
The naming of the members is different, but it's basically the same idea as the struct HashCode type, but with overloads for other complex types (as in my suggestion #2 above). You could use that, or just apply the techniques there to your implementation of struct HashCode, preserving the naming conventions used in it.
I've got a class which consists of two strings and an enum. I'm trying to use instances of this class as keys in a dictionary. Unfortunately I don't seem to be implementing IEquatable properly. Here's how I've done it:
public enum CoinSide
{
Heads,
Tails
}
public class CoinDetails : IComparable, IEquatable<CoinDetails>
{
private string denomination;
private string design;
private CoinSide side;
//...
public int GetHashCode(CoinDetails obj)
{
return string.Concat(obj.Denomination, obj.Design, obj.Side.ToString()).GetHashCode();
}
public bool Equals(CoinDetails other)
{
return (this.Denomination == other.Denomination && this.Design == other.Design && this.Side == other.Side);
}
}
However, I still can't seem to look up items in my dictionary. Additionally, the following tests fail:
[TestMethod]
public void CoinDetailsHashCode()
{
CoinDetails a = new CoinDetails("1POUND", "1997", CoinSide.Heads);
CoinDetails b = new CoinDetails("1POUND", "1997", CoinSide.Heads);
Assert.AreEqual(a.GetHashCode(), b.GetHashCode());
}
[TestMethod]
public void CoinDetailsCompareForEquality()
{
CoinDetails a = new CoinDetails("1POUND", "1997", CoinSide.Heads);
CoinDetails b = new CoinDetails("1POUND", "1997", CoinSide.Heads);
Assert.AreEqual<CoinDetails>(a, b);
}
Would someone be able to point out where I'm going wrong? I'm sure I'm missing something rather simple, but I'm not sure what.
You class has to override Equals and GetHashCode:
public class CoinDetails
{
private string Denomination;
private string Design;
private CoinSide Side;
public override bool Equals(object obj)
{
CoinDetails c2 = obj as CoinDetails;
if (c2 == null)
return false;
return Denomination == c2.Denomination && Design == c2.Design;
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 23 + (Denomination ?? "").GetHashCode();
hash = hash * 23 + (Design ?? "").GetHashCode();
return hash;
}
}
}
Note that i've also improved your GetHashCode algorithm according to: What is the best algorithm for an overridden System.Object.GetHashCode?
You could also pass a custom IEqualityComparer<CoinDetail> to the dictionary:
public class CoinComparer : IEqualityComparer<CoinDetails>
{
public bool Equals(CoinDetails x, CoinDetails y)
{
if (x == null || y == null) return false;
if(object.ReferenceEquals(x, y)) return true;
return x.Denomination == y.Denomination && x.Design == y.Design;
}
public int GetHashCode(CoinDetails obj)
{
unchecked
{
int hash = 17;
hash = hash * 23 + (obj.Denomination ?? "").GetHashCode();
hash = hash * 23 + (obj.Design ?? "").GetHashCode();
return hash;
}
}
}
Now this works and does not require CoinDetails to override Equals+GetHashCode:
var dict = new Dictionary<CoinDetails, string>(new CoinComparer());
dict.Add(new CoinDetails("1POUND", "1997"), "");
dict.Add(new CoinDetails("1POUND", "1997"), ""); // FAIL!!!!
I need to super fast store and retrieve values by two integer keys.
So I have input values uint Id1, uint Id2 and need to get uint Count.
Also I know max value of Id1 and Id2 (it is about 5 000 000).
My current implementation takes about 70% of application work time and it might be a few days.
It just use standard .net dictionaries and of course can be improved. But I guess it is a very useful operation in computer science and no doubt more efficient algorithms exists.
Here is my implementation
void Main()
{
var rep = new Repository();
var sw = new Stopwatch();
sw.Start();
for (uint i = 0; i < 10000; i++)
{
for (uint j = 0; j < 1000; j++)
{
rep.Add(new DomainEntity(){Id1 = i, Id2 = j, Count = 1});
}
}
for (uint i = 0; i < 10000; i++)
{
for (uint j = 0; j < 1000; j++)
{
rep.GetDomainEntityByIds(i,j);
}
}
sw.Stop();
Console.WriteLine ("Elapsed:{0}", sw.Elapsed);
}
public class Repository
{
private readonly Dictionary<Tuple<UInt32, UInt32>, UInt32> _dictStore;
public Repository()
{
_dictStore = new Dictionary<Tuple<uint, uint>, uint>();
}
public uint Add(DomainEntity item)
{
var entry = MapToTableEntry(item);
_dictStore.Add(entry.Key,entry.Value);
return 0;
}
public void Update(DomainEntity item)
{
var entry = MapToTableEntry(item);
_dictStore[entry.Key] = entry.Value;
}
public IEnumerable<DomainEntity> GetAllItems()
{
return _dictStore.Select(MapToDomainEntity);
}
public DomainEntity GetDomainEntityByIds(uint articleId1, uint articleId2)
{
var tuple = new Tuple<uint, uint>(articleId1, articleId2);
if (_dictStore.ContainsKey(tuple))
{
return MapToDomainEntity(new KeyValuePair<Tuple<uint, uint>, uint>(tuple, _dictStore[tuple]));
}
return null;
}
private KeyValuePair<Tuple<uint, uint>, uint> MapToTableEntry(DomainEntity item)
{
return new KeyValuePair<Tuple<uint, uint>, uint>(new Tuple<uint, uint>(item.Id1,item.Id2), item.Count);
}
private DomainEntity MapToDomainEntity(KeyValuePair<Tuple<uint, uint>, uint> entry)
{
return new DomainEntity
{
Id1 = entry.Key.Item1,
Id2 = entry.Key.Item2,
Count = entry.Value,
};
}
}
public class DomainEntity
{
public uint Id1 { get; set; }
public uint Id2 { get; set; }
public uint Count { get; set; }
}
One minor(?) improvement, you can use TryGetValue to avoid to lookup the dictionary twice:
public DomainEntity GetDomainEntityByIds(uint articleId1, uint articleId2)
{
var tuple = new Tuple<uint, uint>(articleId1, articleId2);
uint value;
if (_dictStore.TryGetValue(tuple, out value))
{
return MapToDomainEntity(new KeyValuePair<Tuple<uint, uint>, uint>(tuple, value));
}
return null;
}
What you want to do is create an efficient dictionary using an efficient key & hash. Since the dictionary always uses a 32 bit value and you have around 45 bits of data, you can't create a unique hash, but you should do your best.
Always use TryGetValue() rather than a double lookup.
When using dictionaries with value type keys, use a custom IEqualityComparer passed as the argument to the dictionary constructor.
Use a custom hash code to try to squeeze the maximum amount of information from the subkeys into the 32 bit hash.
Example:
public class Storage
{
private Dictionary<Key, DomainObject> dict;
public Storage()
{
dict = new Dictionary<Key, DomainObject>(Key.Comparer.Instance)
}
public DomainObject Get(uint a, uint b)
{
DomainObject obj;
dict.TryGetValue(new Key(a,b), out obj);
return obj;
}
internal struct Key
{
internal readonly uint a;
internal readonly uint b;
public Key(uint a, uint b)
{
this.a = a;
this.b = b;
}
internal class Comparer : IEqualityComparer<Key>
{
internal static readonly Comparer Instance = new Comparer();
private Comparer(){}
public bool Equals(Key x, Key y)
{
return x.a == y.a && x.b == y.b;
}
public int GetHashCode(Key x)
{
return (int)((x.a & 0xffff) << 16) | (x.b & 0xffff));
}
}
}
}
You're doing a lot of extra work in there, converting to and from KeyValuePair. Also, DomainEntity is a reference type, so you probably should just store references to those in the dictionary rather than having to create them from the key and value every time you look one up.
Create your dictionary as:
var _dictStore = new Dictionary<Tuple<uint, uint>, DomainEntity>();
Then:
public uint Add(DomainEntity item)
{
var key = new Tuple<uint, uint>(item.Id1, item.Id2);
_dictStore.Add(key, item);
return 0;
}
And lookup:
public DomainEntity GetDomainEntityByIds(uint articleId1, uint articleId2)
{
var key = new Tuple<uint, uint>(articleId1, articleId2);
DomainEntity value;
if (!_dictStore.TryGetValue(key, out value))
{
value = null;
}
return value;
}