Search code examples
c#.netabstract-classsubclass

Return child type from a method on an abstract class or force two constructors on child with different parameters types


I have an abstract class TypedIdValueBase which has subclasses like UserId or ImageId. I don't know how but I would like to have a Parse method (or a second constructor which is taking a string in parameter and not a Guid) and call it like that:

Guid valueG = Guid.NewGuid();

UserId userId1 = UserId.Parse(valueG ); //or new UserId(valueG )
ImageId imageId1 = ImageId.Parse(valueG ); // or new ImageId(valueG )

string valueS = "0f8fad5b-d9cb-469f-a165-70867728950e";

UserId userId2 = UserId.Parse(valueS ); //or new UserId(valueS )
ImageId imageId2 = ImageId.Parse(valueS ); // or new ImageId(valueS )

I would like to force each child class to have that method or a second constructor with a string parameter but I really don't know how to handle it. I know about some "simple" answers like that: Abstract Method That Returns an Instance of Derived Class

Someone already linked my previous question to another answer without reading well what I want to do. Pay attention to the type of the variables or parameters before to link and close it to an answer which is not answering the question. Thank you a lot.

The best I had was something like that:

string value = "0f8fad5b-d9cb-469f-a165-70867728950e";
UserId userId = (UserId)UserId.Parse(value); //or new TypedIdValueBase.Parse<UserId>(value)

Does anyone have an idea on how to do something like that ?

Here is the code without traces of my attempts:

public abstract class TypedIdValueBase : IEquatable<TypedIdValueBase>
{
    public Guid Value { get; private set; }

    protected TypedIdValueBase(Guid value)
    {
        if (value == Guid.Empty)
        {
            throw new InvalidOperationException("Id value cannot be empty!");
        }

        Value = value;
    }
}

public class UserId : TypedIdValueBase
{
    public UserId(Guid value) : base(value)
    {
    }
}

public class ImageId : TypedIdValueBase
{
    public ImageId(Guid value) : base(value)
    {
    }
}

Solution

  • Good day,

    I found a way to have something like I wanted but I'm still not sure if it's a good thing to do.

    With the code below I can do:

    UserId userId = UserId.Parse(stringValue);
    UserId userId2 = new UserId(guidValue);
    
    
    public class UserId : TypedIdValueBase<UserId>
    {
        public UserId(Guid value) : base(value)
        {
        }
    }
    
    public abstract class TypedIdValueBase<T> : TypedIdValueBase where T : TypedIdValueBase
    {
        public static T Parse(string value)
        {
            var obj = (T)new object();
            obj.Value = Guid.Parse(value);
            return obj;
        }
    
        protected TypedIdValueBase(Guid value) : base(value)
        {
        }
    }
    
    public abstract class TypedIdValueBase : IEquatable<TypedIdValueBase>
    {
        private Guid _value;
    
        public Guid Value
        {
            get => _value;
            protected internal set
            {
                CheckValue(value);
                _value = value;
            }
        }
    
        protected TypedIdValueBase(Guid value)
        {
            CheckValue(Value);
            Value = value;
        }
    
        private void CheckValue(Guid value)
        {
            if (value == Guid.Empty)
            {
                throw new InvalidOperationException("Id value cannot be empty!");
            }
        }
    
        public bool Equals(TypedIdValueBase other) => Value == other?.Value;
    
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }
    
            return obj is TypedIdValueBase other && Equals(other);
        }
    
        public override int GetHashCode() => Value.GetHashCode();
    
        public static bool operator ==(TypedIdValueBase obj1, TypedIdValueBase obj2)
        {
            if (Equals(obj1, null))
            {
                if (Equals(obj2, null))
                {
                    return true;
                }
    
                return false;
            }
    
            return obj1.Equals(obj2);
        }
    
        public static bool operator !=(TypedIdValueBase x, TypedIdValueBase y) => !(x == y);
    }