0

I'm working with some complex generics to allow inheritance from root class with state information and I don't quite understand two different scenarios (from my perspective it's the same, but probably I'm wrong):

Why this is ok:

    class Client
    {
        public interface IState { }

        protected class State : IState { }

        class States<T> where T : IState
        {
            public States(T state) { }
        }

        public Client()
        {
            State state = new State();
            States<IState> states = new States<IState>(state);
        }
    }

But this is not:

    class Client<T> where T : Client<T>.IState
    {
        public interface IState { }

        protected class State : IState { }

        class States
        {
            public States(T state) { }
        }


        public Client()
        {
            State state = new State();
            States states = new States(state); //Error -> Can't convert Client<T>.State to T
        }
    }
Feralnex
  • 61
  • 5
  • Does this answer your question? [How does c# handle nested (generic) types?](https://stackoverflow.com/questions/28774970/how-does-c-sharp-handle-nested-generic-types) – knittl Oct 05 '22 at 19:48
  • @knittl Unfortunately no : / – Feralnex Oct 05 '22 at 20:00
  • The comment in your code is wrong. The problem which prevents the compilation is that there is not a way to convert from Client.State to T. In fact the constructor of the States class expects a parameter of type T. Why do you expect the compiler being able to convert from Client.State to T ? They are actually different types with no relations. – Enrico Massone Oct 05 '22 at 20:00
  • @EnricoMassone I expect that it would accept it because State is inheriting from IState hence from Client.IState. How they're different types with no relations? Can you show me an example? – Feralnex Oct 05 '22 at 20:05
  • `State state = new State()` must be `T state = new T()` (and T must have `new` constraint). State != T – knittl Oct 05 '22 at 20:10
  • @knittl I know it will work, but State inherits from Client.IState (T) so why States constructor can't accept it? – Feralnex Oct 05 '22 at 20:11
  • 2
    @Feralnex because the States constructor expects a concrete `T`, not a `Client.IState`. – knittl Oct 05 '22 at 20:12
  • @knittl Ok i after some thought i managed to understand it. Thanks. The problem was that second one can accept interface derived from Class.IState which State doesn't implement hence the Error. – Feralnex Oct 05 '22 at 20:16
  • 2
    @Feralnex If it helps, Dog:Animal and Cat:Animal but Dog != Cat. `T:IState` and `State:IState` but `T != State`. You cannot pass a `State` object to a constructor that accepts `T`. – Luke Briggs Oct 05 '22 at 20:20
  • 1
    @LukeBriggs Thanks, examples will always be useful ; ) – Feralnex Oct 05 '22 at 20:24
  • Why dont you pull `Client.IState` out to an independant interface, then you could do: `class Client where T : IState`. – Poul Bak Oct 05 '22 at 20:33

3 Answers3

1

The code reported here is quite convoluted and difficult to understand. I would suggest, if possible, to change it and avoid such a complex nesting of types.

That said, let's try to understand why your code is not compiling.

First of all, let's notice that the comment reported in your question is wrong. The error preventing to compile is that there is no way to convert from Client<T>.State to the generic type parameter T. Notice that the state variabile has a compile time type of Client<T>.State, but the constructor of the States class requests a parameter of type T.

The only thing you know about the T type is that it implements the Client<T>.IState interface. The Client<T>.State class also implements the Client<T>.IState interface, but this is not enough for an implicit conversion between Client<T>.State and T to exist.

To generalize: having two types implementing the same interface does not imply a conversion between them to exist.

As an example, consider the following code.

public interface IFoo {}

public class Foo: IFoo {}

public class Bar: IFoo {}

both Foo and Bar implements the IFoo interface, but there is no way to convert between Foo and Bar.

Enrico Massone
  • 6,464
  • 1
  • 28
  • 56
0
class States
{
    public States(T state) { }
}

Has no idea what T is. It registers as a CS0246 error, which sounds weird but is the true issue at stake here, the compiler doesn't know that you mean T the generic, and not a T class that you may or may not have created yourself

Generic T is not an operator on its own, it needs the <T> for the compiler to know that you intend to use type parameters

You must annotate the class (since this is a constructor) or any other methods to declare upfront it uses generics

class States<T> where T : SomeConstraint
{
    public States(T state) { }
}
Narish
  • 607
  • 4
  • 18
0

Consider the following:

class NotAState : Client<NotAState>.IState {}
class Client<T> where T : Client<T>.IState
{
    public interface IState { }

    protected class State : IState { }

    class States
    {
        public States(T state) { }
    }


    public Client()
    {
        State state = new State();
        States states = new States(state); //Error -> Can't convert Client<T>.State to T
    }
}

var client = new Client<NotAState>();

Here, States must be constructed by passing an instance of NotAState, yet you pass in a State. A state is not a notastate :)

knittl
  • 246,190
  • 53
  • 318
  • 364