3
public interface INestedInterfaceTest<TChildType>
    where TChildType : INestedInterfaceTest<TChildType>
{
     List<TChildType> children { get; set; }
}

public abstract class NestedInterfaceTest : INestedInterfaceTest<NestedInterfaceTest>
{
    public List<NestedInterfaceTest> children { get; set; }

    public TNestedInterface GetNestedInterface<TNestedInterface>()
        where TNestedInterface : NestedInterfaceTest, new()
    {
        return GateWay<TNestedInterface>.GetNestedInterface();
    }
}

public class GateWay<TNestedInterface>
    where TNestedInterface : class, INestedInterfaceTest<TNestedInterface>, new()
{
    public static TNestedInterface GetNestedInterface()
    {
        return new TNestedInterface();
    }
}

Things go wrong at the GetNestedInterface method in the abstract class. The error message is: The type 'TNestedInterface' must be convertible to 'INestedInterfaceTest' in order to use it as parameter 'TNestedInterface' in the generic class 'GateWay'.

But..., my abstract class NestedInterfaceTest implements the INestedInterfaceTest interface. What am I missing here?

The following does work, without the recursive interface implementation:

public interface INestedInterfaceTest
{
}

public abstract class NestedInterfaceTest : INestedInterfaceTest
{
    public List<NestedInterfaceTest> children { get; set; }

    public TNestedInterface GetNestedInterface<TNestedInterface>()
        where TNestedInterface : NestedInterfaceTest, new()
    {
        return GateWay<TNestedInterface>.GetNestedInterface();
    }
}

public class GateWay<TNestedInterface>
    where TNestedInterface : class, INestedInterfaceTest, new()
{
    public static TNestedInterface GetNestedInterface()
    {
        return new TNestedInterface();
    }
}

It seems that it goes wrong in the recursive implementation.

Marsupilami
  • 61
  • 1
  • 8
  • Why don't just just extend the `GetNestedInterface` method constraint to `where TNestedInterface : NestedInterfaceTest, INestedInterfaceTest, new()`? – MarcinJuraszek Oct 07 '13 at 20:52
  • Because I don't want to bother the 'user' who implements my abstract class with the interfaces I've set up for them. Regardless of my previous point, why would I need to? The abstract class implements the required interface with that specific signature. It feels like I'm repeating myself, where I shouldn't need to. – Marsupilami Oct 07 '13 at 20:57
  • I've edited my original post to illustrate the essence of the problem. – Marsupilami Oct 07 '13 at 21:19
  • The constraint `T where T: I` means that `T` is exactly `I`, i.e. it's implemented right there in the `T` class, it does not meat `T-or-base-class-of-T` – SWeko Oct 07 '13 at 21:21
  • 1
    I may have answered my own question. Would you please review it? I value any and all well founded reviews. – Marsupilami Oct 07 '13 at 21:45
  • Can I ask what you are actually modeling? – SWeko Oct 07 '13 at 22:35

3 Answers3

1

You're missing a generic constraint on GetNestedInterface<>(). Change it to this:

public TNestedInterface GetNestedInterface<TNestedInterface>()
    where TNestedInterface : 
        NestedInterfaceTest, 
        INestedInterfaceTest<TNestedInterface>, // new
        new()
{
    return GateWay<TNestedInterface>.GetNestedInterface();
}

Note the second constraint is new.

recursive
  • 83,943
  • 34
  • 151
  • 241
  • Please refer to the answer I gave MarcinJuraszek. – Marsupilami Oct 07 '13 at 20:58
  • 1
    The compiler requires that constraint in accordance with the language specification. An argument could be made that in some future version, there should be more in-depth inference should be done. – recursive Oct 07 '13 at 21:18
  • That's a very concise and to the point answer. Thank you very much! It's a pity there's this limitation right now, but I've found a workaround. – Marsupilami Oct 07 '13 at 23:27
0

NestedInterfaceTest implements INestedInterfaceTest<NestedInterfaceTest> and the requirement on the TNestedInterface in the GetNestedInterface is that it should implement INestedInterfaceTest<TNestedInterface> too.

If I create a subclass of NestedInterfaceTest that does not re-implement the INestedInterfaceTest<> interface, that constraint will not be satisfied:

//does not implement INestedInterfaceTest<Derived>
class Derived : NestedInterfaceTest {}
class Derived2 : NestedInterfaceTest {}

Derived d = new Derived();
// breaks because GateWay<Derived2> cannot be constructed
d.GetNestedInterface<Derived2>();

Simplest work-around I see, that might not fit into your object model is to redefine the constraint on the GateWay class with:

public class GateWay<TNestedInterface>
  where TNestedInterface : class, INestedInterfaceTest<NestedInterfaceTest>, new()

This way, any class that inherits from NestedInterfaceTest will fulfill the constraint without the need to reimplement the interface, because NestedInterfaceTest fulfills it. It might be even handier to do:

public class GateWay<TNestedInterface>
  where TNestedInterface : class, NestedInterfaceTest, new()

Anyway, even if you manage to get this to work, it's still confusing, both for the consumer of the class, and for the (eventual) maintainer. Also, you can almost never be sure that some misuses will not be possible

SWeko
  • 30,434
  • 10
  • 71
  • 106
  • But the abstract class NestedInterfaceTest itself implements the required interface. If I would replace the recursive interface implementation with another (abstract) class, it works. Only when there's recursion does it seem to fail. – Marsupilami Oct 07 '13 at 21:04
  • `NestedInterfaceTest` is `INestedInterfaceTest`, a `Derived2` **is not** `INestedInterfaceTest` – SWeko Oct 07 '13 at 21:05
  • Bear with me, I'm trying to understand. With generics, one sets restrictions on what that generic implementation ought to be. With that, the interface restriction demands that the object is a class, that implements that specific interface (which it does) and has a parameterless constructor, which is specified in the hand off. Let me change my code a little and I'll add it in my answer to illustrate my point. I've adjusted my original post to illustrate my point. Please have a look. Perhaps you've already given me the answer and I'm not linking it. If so, my apologies. It's just not landing. – Marsupilami Oct 07 '13 at 21:13
  • 1
    `the interface restriction demands that the object is a class`- careful here, this is my *biggest* bug-bear with the C# spec. The generic constraint `class` does not constrain to a *class type*, it constrains to a *reference type*. I can't work out for the life of me why the spec designers chose such a misleading keyword. See (in the v5) spec 10.1.5- `A primary constraint can be a class type or the _reference type constraint_ class` – nicodemus13 Oct 13 '15 at 15:59
0

I think I solved my own problem. Please review the code and let me know what you think:

public interface INestedInterfaceTest<TChildType>
    where TChildType : INestedInterfaceTest<TChildType>
{
    List<TChildType> children { get; set; }
}

public abstract class NestedInterfaceTest<TChildNestedInterface> : INestedInterfaceTest<TChildNestedInterface>
    where TChildNestedInterface : INestedInterfaceTest<TChildNestedInterface>
{
    public List<TChildNestedInterface> children { get; set; }

    public virtual TNestedInterface GetNestedInterface<TNestedInterface>()
        where TNestedInterface : NestedInterfaceTest<TChildNestedInterface>, INestedInterfaceTest<TNestedInterface>, new()
    {
        return GateWay<TNestedInterface>.GetNestedInterface();
    }
}

public class GateWay<TNestedInterface>
    where TNestedInterface : class, INestedInterfaceTest<TNestedInterface>, new()
{
    public static TNestedInterface GetNestedInterface()
    {
        List<TNestedInterface> nestedChildren = new List<TNestedInterface>();
        return new TNestedInterface
            {
                children = nestedChildren
            };
    }
}

public class NestedClass : NestedInterfaceTest<NestedClass>
{
    public NestedClass GetNestedClass()
    {
        return GetNestedInterface<NestedClass>();
    }
}

That did the trick alright and it prevents the interface from 'leaking out'.

Marsupilami
  • 61
  • 1
  • 8
  • You can still do: `public class EvilNestedClass : NestedInterfaceTest`, so I don't really see the use of the whole contraption. – SWeko Oct 07 '13 at 22:33
  • The reason for it is, if you put the interface restriction on the **public TNestedInterface GetNestedInterface()** in the abstract class, the implementing class will have to implement the interface explicitly. I didn't want the interfaces to go beyond the abstract class definitions. Now it's limited to a generic type parameter. And I can live with that. In fact, it makes sense. You could have a different implementation of the abstract class for child classes, as long as they implement the abstract class. – Marsupilami Oct 07 '13 at 23:25