1

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?

2
  • Why do you think option 1 would be hard to use?
    – JonasH
    Sep 6, 2021 at 14:55
  • Hard is a bit overexagerating... The thing is that you'd always have to access the Guid property, but that's also the case for the other options. If I could inherit from Guid directly that step would not be necessary. Sep 7, 2021 at 5:53

2 Answers 2

2

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();
}
0

The use of a GUID as a unique Identifier is not necessarily user friendly. For example test automation would have to identify the GUID in the object, copy it and place into the test. This is why we see IDs in often look more like "AccountsReceivable" as they are just are better suited for readability.

If you are targeting Web Page ID's then there's no chance for duplicate ids within that page. So why use a GUID? Favor readability instead.

However in the event you are not using WebPages and these are just C# objects, then the class construct is a good way to contain the ids.

There's two ways to do it,

  • Provide an ID field of type string or Guid or
  • Proved and ID field of some other class or struct type. This is called containment and is great when you want that class or other type to be reused throughout your application.

Structs are commonly used but It's my opinion that Classes are used more often in the C# world.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.