I have came up with this using the Hashing algorithm on this answer GetHashCode. Obviously, there is always the problem of conflicts, for different valued objects having the same hash code. But that problem is not solvable, because you are essentially transforming from potentially an infinite domain to a very limited range, mostly 32 bits, for Guid's, 128 bits. The only get around for that sort of problem is, even though the chance of it happening is pretty low, as you said, implementing a look-up table.
public static class ConsistentGuid
{
public static System.Guid Generate(object obj)
{
var bytes = new byte[16];
var type = obj.GetType();
var features = new object[]
{
type,
obj
};
BitConverter.GetBytes(LongHash(features))
.CopyTo(bytes, 0);
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
features = new object[properties.Length];
for (int i = 0; i < properties.Length; i++)
features[i] = properties[i].GetValue(obj);
BitConverter.GetBytes(LongHash(features))
.CopyTo(bytes, 8);
return new System.Guid(bytes);
}
public static int Hash(object[] features, uint seed = 2166136261)
{
// https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode/263416#263416
unchecked // Overflow is fine, just wrap
{
int hash = (int)seed;
for (int i = 0; i < features.Length; i++)
{
if (features[i] == null) // Suitable nullity checks etc, of course :)
continue;
hash = (hash * 16777619) ^ features[i].GetHashCode();
}
return hash;
}
}
private static long LongHash(object[] features, ulong seed = 2166136261)
{
// https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode/263416#263416
unchecked // Overflow is fine, just wrap
{
long hash = (long)seed;
for (int i = 0; i < features.Length; i++)
{
if (features[i] == null) // Suitable nullity checks etc, of course :)
continue;
hash = (hash * 16777619) ^ features[i].GetHashCode();
}
return hash;
}
}
}
Here are the tests passed;
public class ConsistentGuidTests
{
[Fact]
public void Referencewise_Equal_Objects_Should_Generate_Same_Guids()
{
var obj = new object();
var obj2 = obj;
var guid1 = ConsistentGuid.Generate(obj);
var guid2 = ConsistentGuid.Generate(obj2);
Assert.True(ReferenceEquals(obj, obj2));
Assert.Equal(guid1, guid2);
}
[Fact]
public void ValueObjects_Of_DifferentTypes_Should_Generate_Different_Guids()
{
var obj = new object();
var other = new int();
var guid1 = ConsistentGuid.Generate(obj);
var guid2 = ConsistentGuid.Generate(other);
Assert.NotEqual(guid1, guid2);
}
[Fact]
public void ValueObjects_With_Same_Values_Should_Generate_Same_Guids()
{
var obj = 123;
var other = 123;
var guid1 = ConsistentGuid.Generate(obj);
var guid2 = ConsistentGuid.Generate(other);
Assert.False(ReferenceEquals(obj, other));
Assert.Equal(guid1, guid2);
}
[Fact]
public void ValueObjects_With_Different_Values_Should_Generate_Different_Guids()
{
var obj = 123;
var other = 124;
var guid1 = ConsistentGuid.Generate(obj);
var guid2 = ConsistentGuid.Generate(other);
Assert.NotEqual(guid1, guid2);
}
class AReferenceType
{
public int SomeProperty { get; set; }
public string SomeOtherProperty { get; set; }
public AReferenceType(int a, string b)
{
SomeProperty = a;
SomeOtherProperty = b;
}
public override int GetHashCode()
{
return ConsistentGuid.Hash(new object[]
{
SomeProperty,
SomeOtherProperty
});
}
}
[Fact]
public void ReferenceObjects_With_Same_Values_Should_Generate_Same_Guids()
{
var a = 123;
var b = "asd";
var obj = new AReferenceType(a, b);
var other = new AReferenceType(a, b);
var guid1 = ConsistentGuid.Generate(obj);
var guid2 = ConsistentGuid.Generate(other);
Assert.False(ReferenceEquals(obj, other));
Assert.Equal(obj.GetHashCode(), other.GetHashCode());
Assert.Equal(guid1, guid2);
}
[Fact]
public void ReferenceObjects_With_Different_Values_Should_Generate_Different_Guids()
{
var a = 123;
var b = "asd";
var obj = new AReferenceType(a, b);
var other = new AReferenceType(a + 1, b);
var guid1 = ConsistentGuid.Generate(obj);
var guid2 = ConsistentGuid.Generate(other);
Assert.NotEqual(guid1, guid2);
}
}