Question

I have a "minimal state" type that contains some data and can generate another instance of minimal state:

public interface IMinimalState
{
   IMinimalState generate();
   int getData();
}

I also have a "normal state" that contains more data and can generate an instance of normal state:

public interface IState
{
   IState generate();
   int getData();
   int getAnotherData();
}

As far as I can tell, IState should be a subtype of IMinimalState. (Anything that requires an IMinimalState should be just as happy when it gets an IState) However, I cannot get the compiler to accept that.

Example:

   public interface IMinimalState
   {
      IMinimalState generate();
      int getData();
   }

   public interface IState : IMinimalState
   {
      int getAnotherData();
   }

   public class MinimalState : IMinimalState
   {
      public IMinimalState generate() { return this; }
      public int getData() { return 0; }
   }

   public class State : IState
   {
      public IState generate() { return this; }  //Compiler Error, return type should be IMinimalState
      public int getData() { return 1; }
      public int getAnotherData() { return 2; }
   }

Fair enough, let's try generic interfaces:

   public interface IMinimalState<out T> where T : IMinimalState<T>
   {
      T generate();
      int getData();
   }

   public interface IState<out T> : IMinimalState<T> where T : IState<T>
   {
      int getAnotherData();
   }

   public class MinimalState : IMinimalState<MinimalState>
   {
      public MinimalState generate() { return this; }
      public int getData() { return 0; }
   }

   public class State : IState<State>
   {
      public State generate() { return this; }
      public int getData() { return 1; }
      public int getAnotherData() { return 2; }
   }

No compiler errors yet. But State is still not a subtype of MinimalState, and IState<State> is not a subtype of IMinimalState<MinimalState>. I tried many other combinations but had no success.

Is there a way to make IState a subtype of IMinimalState? If not,

  • is it because .Net type system is not powerful enough?

  • or, am I wrong to think that IState is really a subtype of IMinimalState? (Is there a scenario when the program will crash if something that requires an IMinimalState receives a IState?)

Was it helpful?

Solution

You can use new keyword to change the return type of generate, and then implement both variations of generate in State class

   public interface IMinimalState
   {
      IMinimalState generate();
      int getData();
   }

   public interface IState : IMinimalState
   {
      new IState generate();      
      int getAnotherData();
   }

   public class State : IState
   {
      public IState generate() { return this; } 
      IMinimalState IMinimalState.generate() { return  generate(); }           
      ...
   }

OTHER TIPS

If you change the return type - you're changing the contract - you can't model that with IMinimalState.

I'd just simplify a bit e.g.

public interface IMinimalState
{
    IMinimalState generate();
    int getData();
}

public interface IState : IMinimalState
{
    // IState generate();
    int getAnotherData();
}

public class MinimalState : IMinimalState
{
    public IMinimalState generate() { return this; }
    public int getData() { return 0; }
}

public class State : IState
{
    IMinimalState IMinimalState.generate() { return this.generate(); }
    // IState IState.generate() { return this; }
    public IState generate() { return this; }
    public int getData() { return 1; }
    public int getAnotherData() { return 2; }
}

Note: your 'use-case' is essential here (and missing) - and drives your design. You should always supply that prior to making design decision, probably the first thing to do.

Normally if you're storing and iterating over the base interface IMinimalState - that's all you need.

If you're accessing the object knowing that it's State you can use its public method for that to get the strong-type.

If you'd still want - you can have both (just uncomment the two lines).

And the last option - often used in more complex cases - is to use a Visitor to differentiate in between IMinimalState and IState - when you have just a list of base interfaces. E.g. that's used by expressions where you need to redefine types of parameters, sub-expressions etc. If you're interested I can post that later.

You need to return IMinimalState because this is what the interface says.

public class State : IState
{
  public IMinimalState generate() { return this; }  // Fixed
  public int getData() { return 1; }
  public int getAnotherData() { return 2; }
}

If you later need to access the IState members on a reference of IMinimalState you need to cast it

var state = minimalState as IState;
if (state != null)
{
    // Work on IState
}

You could use generics:

public interface IMinimalState<T>
{
    T generate();
    int getData();
}

public interface IState : IMinimalState<IState>
{
    IState generate();
    int getAnotherData();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top