How to Declare an ID Value Object in C#

cdomain-driven-design

For some more type safety I wanted to introduce a PageId type in my application. However, I am not totally sure what the best option to do so is.

I want the IDs to be GUIDs, so I thought I just create a PageId class which inherits from the Guid class. Unfortunately the Guid is a struct, which does not support inheritance. I think that would have been the nicest solution, because it would be a value type and easy to map using ORMs, but it seems that this solution is of the table.

Now my question is how should it look instead? I can think of a few options:

  1. Build a PageId struct that contains a Guid property (I guess that can be hard to use in code at times)
  2. The same as 1., but using a class instead of a struct
  3. Using the new record type

Or is there another obvious option I am overlook?

Best Answer

As you already mentioned for newer C# version you can wrap Guid value with the record type. You will get required equality functionality for "free".

public record PageId(Guid Value);

For older C# versions you can wrap Guid with the dedicated struct type, but then you need to implement equality by yourself.
For multiple types you can accept possible "duplication", it would be "safe duplication", because implementation will not change.

public readonly struct PageId
{
    public static PageId New() => new PageId(Guid.NewGuid());

    public Guid Value { get; }

    public PageId(Guid value)
    {
        Value = value;
    }

    public override bool Equals(object obj)
    {
        return obj is PageId && Equals(identifier);
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }

    public bool Equals(PageId identifier)
    {
        return Value == identifier.Value;
    }

    public static bool operator ==(PageId first, PageId second)
    {
        return first.Equals(second);
    }

    public static bool operator !=(PageId first, PageId second)
    {
        return first.Equals(second) == false;
    }
}

You can introduce some helper extension methods

public static class PageExtensions
{
    public static PageId AsPageId(this Guid id) => new PageId(id);

    public static PageId? AsPageId(this Guid? id) => id?.AsPageId();
}