Search code examples
c#genericsstrong-typing

Strongly typed IDs with generic class


I want to change some of my entities IDs to strongly typed. And in order to avoid too much code refactoring I used implicit conversions.

Here's what I have so far:

public class FooBarId
{
    private readonly Guid _id;

    public FooBarId(Guid id)
    {
        _id = id;
    }

    public Guid Id
    {
        get { return _id; }
    }

    public static implicit operator Guid(FooBarId id)
    {
        return id.Id;
    }

    public static implicit operator FooBarId(Guid id)
    {
        return new FooBarId(id);
    }
}

This code works fine and I don't have to change anything else in my code.

But I would like to create much more such classes and in order to avoid repeating myself I created a generic class:

public class CustomId<T>
{
    private readonly T _id;

    public CustomId(T id)
    {
        _id = id;
    }

    public T Id
    {
        get { return _id; }
    }

    public static implicit operator T(CustomId<T> id)
    {
        return id.Id;
    }

    public static implicit operator CustomId<T>(T id)
    {
        return new CustomId<T>(id);
    }
}

And tried to inherit it in my FooBarId class:

public class FooBarId : CustomId<Guid>
{
}

And then my code breaks with an exception where FooBarId is used:

Argument type 'System.Guid' is not assignable to parameter type 'FooBarId'.

When I try to cast it like this foo.bar((FooBarId)new Guid()) - code compiles, but throws an exception later in program execution:

<System.InvalidCastException> (Unable to cast object of type 'CustomId1[System.Guid]' to type 'FooBarId'.)

What am I missing here?


Solution

  • This works for me:

    class Program
    {
        static void Main(string[] args)
        {
            Guid result1 = (CustomId<Guid>)new CustomId<Guid>(Guid.NewGuid());
            Guid result2 = (CustomId<Guid>)new FooBarId();
            Guid result3 = (FooBarId)new FooBarId();
            Guid result4 = new FooBarId();
    
            CustomId<Guid> cIResult = Guid.NewGuid();
        }
    }
    
    public class CustomId<T>
    {
        private readonly T _id;
    
        public CustomId(T id)
        {
            _id = id;
        }
    
        public T Id
        {
            get { return _id; }
        }
    
        public static implicit operator T(CustomId<T> id)
        {
            return id.Id;
        }
    
        public static implicit operator CustomId<T>(T id)
        {
            return new CustomId<T>(id);
        }
    }
    
    public class FooBarId : CustomId<Guid>
    {
        public FooBarId()
            : base(Guid.NewGuid())
        {
        }
    }
    

    You cannot do (FooBarId)new Guid() because a CustomId<Guid> is not a FooBarId. You will need to define am implicit operator for each class which converts from Guid to that class.