Duncan Lukkenaer
Duncan Lukkenaer

Reputation: 13984

Enforcing generic interface childs type

I have a generic interface (MyInterface<T>), which is implemented by the class ChildA in the example below:

public interface MyInterface<T>
{
    MyObj<T> GetObj(); // Irrelevant
}

class ChildA : MyInterface<ChildA>
{
    // Irrelevant:
    MyObj<ChildA> GetObj() {
        return new MyObj<ChildA>();
    }
}

This works, but I need to make sure that <T> always has the type of the implementing class, so in this case T should always be of type ChildA, because it is implemented by ChildA.

Another correct implementation could be this, for example:

class ChildB : MyInterface<ChildB> { ... }

But currently, this incorrect implementation is also possible, while it should not be:

class ChildA : MyInterface<ChildB> { ... }

Is there a way to enforce this?

Upvotes: 4

Views: 244

Answers (4)

Fabjan
Fabjan

Reputation: 13676

Is there a way to enforce this?

Well, not with generic constraints. You can do that with reflection though i'd vote against it :

public abstract class BaseChild<T> : MyInterface<T>
{
    protected BaseChild()
    {
        if (typeof(T) != this.GetType())
        {
            throw new InvalidOperationException(string.Format(
                          "Type {0} is not supported as valid type parameter for type {1}",
                             typeof(T).Name, this.GetType().Name));
        }
    }
}

Example :

class ChildA : BaseChild<int> { }

// Bang! throws
var instance = new ChildA(); 

.

class ChildB : BaseChild<ChildB> { }

// Ok here
var instance = new ChildB(); 

Upvotes: 2

Kimi
Kimi

Reputation: 14099

It seems that you should use extension methods instead of enforcing some interface for this purpose

public interface ISomeInterface {}

public class Child: ISomeInterface {}

public class OtherChild : ISomeInterface { }

public static class MyInterfaceExtensions
{
    public static MyObj<T> GetMyObj<T>(this T child) where T : ISomeInterface
    {
        return new MyObj<T>();
    }
}

public static class Test
{
    public static void RunTest()
    {
        var child = new Child();
        var otherChild = new OtherChild();

        MyObj<Child> myObj = child.GetMyObj();
        MyObj<OtherChild> myOtherObj = otherChild.GetMyObj();
    }
}

Upvotes: 1

J&#250;lio Murta
J&#250;lio Murta

Reputation: 545

You cannot do this but you can create your own control comparing the generic type of the interface and the type of your class. See the example:

class ChildA : MyInterface<ChildB>
{
    public ChildA()
    {
        this.ValidateGenericType();
    }

    public MyObj<ChildB> GetObj()
    {
        return new MyObj<ChildB>();
    }

    protected void ValidateGenericType()
    {
        //throws an Exception because ChildB is different of ChilA
        if (this.GetType().Name != this.GetType().GetInterfaces()[0].GetGenericArguments()[0].Name)
        {
            throw new Exception("The generic type must be of type ChildA.");
        }
    }
}

Upvotes: 1

Dmytro Shevchenko
Dmytro Shevchenko

Reputation: 34611

You cannot enforce a generic type argument to be constrained to the implementing type.

The available type constraints are the following:

  • where T : struct
  • where T : class
  • where T : new()
  • where T : <base class name>
  • where T : <interface name>
  • where T : U

There is nothing like where T : self in C#. Actually, it wouldn't even make sense, because such a thing cannot be meaningfully enforced. Besides, it wouldn't fit at all into the covariance/contravariance concepts and would be weird to inherit from, in general.

The closest thing you can do is this:

public interface IMyInterface<T> where T : IMyInterface<T>
{
    MyObj<T> GetObj();
}

Why it wouldn't make sense

Let's say you could do this:

public interface IMyInterface<T> where T : self // this syntax does not exist in C#
{
    MyObj<T> GetObj();
}

Now all implementing types would have to use themselves as the type argument. But you could still do this:

public class ChildC<T> : IMyInterface<T> where T : self
{
    /* ... */
}

Which would go around your restriction.

Upvotes: 7

Related Questions