0

We have to create consistent Guid for an object. I am thinking of going with below approach to create unique guid for object with same properties, somehow it feels wrong.

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return "Person: " + Name + " " + Age;
    }
}

// Below tool is taken from http://stackoverflow.com/questions/2642141/how-to-create-deterministic-guids
Guid guid = GuidUtility.Create(GLOBAL, new Person(..).ToString();

I want .

One other possible solution I was thinking was to save a Map<Guid, serialized Object> in persistent storage and every time while creating a new object see if the object with same properties already exists.

RFC 4122 gurantees with high probability that they will be deterministic and unique per namespace and name . https://www.rfc-editor.org/rfc/rfc4122#page-13 Any suggestions please. Thank you

Community
  • 1
  • 1
Mok
  • 277
  • 1
  • 6
  • 16

1 Answers1

2

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);
    }
}
Community
  • 1
  • 1
welrocken
  • 266
  • 1
  • 9