Hayden
Hayden

Reputation: 2988

Type safety for passing in Types

Currently I am in the phase of refactoring my code after it has been unit tested, and I have some concerns about the refactoring from a design point of view with regards to type safety. My original code looked a bit like this:

Interfaces

public interface IBase
{
    int ID { get; set; }
}

public interface IFirstSub : IBase
{
    string Description { get; set; }
}

public interface ISecondSub : IBase
{
    decimal Total { get; set; }
}

public interface IThirdSub : IBase
{
    int Count { get; set; }
}

public interface IBaseContainer
{
    void Add(IBase baseParam);
}

Implementations

public class FirstContainer : IBaseContainer
{
    public void Add(IBase baseParam)
    {
        if (!(baseParam is IFirstSub || baseParam is ISecondSub))
        {
            throw new ArgumentException(nameof(baseParam));
        }

        // Do Something
    }
}

public class SecondContainer : IBaseContainer
{
    public void Add(IBase baseParam)
    {
        if (!(baseParam is IThirdSub))
        {
            throw new ArgumentException(nameof(baseParam));
        }

        // Do Something
    }
}

With my original implementation of FirstContainer and SecondContainer, it was repeating the same logic at the start of the Add method, so I thought I would refactor the code to look something like this:

public abstract class BaseContainer : IBaseContainer
{
    private readonly List<Type> _types = new List<Type>();

    protected BaseContainer(params Type[] baseTypes)
    {
        _types.AddRange(baseTypes);
    }

    public void Add(IBase baseParam)
    {
        if (_types.All(type => !type.IsInstanceOfType(baseParam)))
        {
            throw new ArgumentException(nameof(baseParam));
        }

        DoSomething(baseParam);
    }

    protected abstract void DoSomething(IBase baseParam);
}

public class ThirdContainer : BaseContainer
{
    public ThirdContainer() : base(typeof(IFirstSub)) { }

    protected override void DoSomething(IBase baseParam)
    {
        // Do Something
    }
}

With this refactoring done, it successfully removes the duplication of the code from the start of the Add method, but my main concern with the refactoring is the fact that the call to the base constructor base(typeof(IFirstSub)) is not really type safe. By that, I mean I can call the base constructor like base(typeof(object)) for example, and it will compile. For the purposes of my project, I'd like to constrain the types to ones that inherit IBase, and enforce at compile time.

Is there anyway to overcome this limitation, or would a new design be needed in order to achieve this?

Upvotes: 0

Views: 87

Answers (1)

John Wu
John Wu

Reputation: 52240

No it's not type safe

Passing and validating types at run-time is not type-safe, as type-safety is a compile-time concept. In my opinion your refactoring effort does not improve the code, and in fact does something quite weird.

Function overloading

If you need a method that accepts either of two types, you can use function overloading:

public class FirstContainer : IBaseContainer
{
    public void Add(IFirstSub param)
    {
        // Do Something
    }
    public void Add(ISecondSub param)
    {
        // Do Something
    }
}

The compiler will automatically choose the right prototype for you, and will not allow anything other than an IFirstSub or ISecondSub.

Create another interface

Another approach requires you to add an interface for the types that have something in common, like this:

interface ICanBeHeldInFirstContainer
{ }

public interface IFirstSub : IBase, ICanBeHeldInFirstContainer
{
    string Description { get; set; }
}

public interface ISecondSub : IBase, ICanBeHeldInFirstContainer
{
    decimal Total { get; set; }
}

Then you do this:

public class FirstContainer : IBaseContainer
{
    public void Add(ICanBeHeldInFirstContainer param)
    {
        // Do Something
    }
}

or this:

public class FirstContainer : IBaseContainer
{
    public void Add<T>(T param) where T : ICanBeHeldInFirstContainer 
    {
        // Do Something
    }
}

Upvotes: 4

Related Questions