Class hierarchy problem (with overall variance!)

Problem:

class StatesChain : IState, IHasStateList {
    private TasksChain tasks = new TasksChain();

     ...

    public IList<IState> States {
        get { return _taskChain.Tasks; }
    }

    IList<ITask> IHasTasksCollection.Tasks {
        get { return _taskChain.Tasks; } <-- ERROR! You can't do this in C#!
                                             I want to return an IList<ITask> from
                                             an IList<IStates>.
    }
}

      

Assuming the returned one IList

will be read-only, I know that what I'm trying to achieve is safe (or not?). Is there a way to do what I am trying to do? I would hate to try to implement the algorithm itself TasksChain

(again!), As it would be error prone and lead to code duplication. Maybe I could just define an abstract chaining and then implement both TasksChain

and StatesChain

from there? Or perhaps implement a class Chain<T>

?

How do you approach this situation?

Details: I have defined the interface ITask

:

public interface ITask {
    bool Run();
    ITask FailureTask { get; }
}

      

and IState

, which inherits from ITask

:

public interface IState : ITask {
    IState FailureState { get; }
}

      

I also defined an interface IHasTasksList

:

interface IHasTasksList {
    List<Tasks> Tasks { get; }
}

      

and IHasStatesList

:

interface IHasTasksList {
    List<Tasks> States { get; }
}

      

Now I have defined a TasksChain

, that is, a class that has some code logic that will manipulate the chain of tasks (beware that TasksChain

itself is a kind ITask

!):

class TasksChain : ITask, IHasTasksList {
    IList<ITask> tasks = new List<ITask>();

    ...

    public List<ITask> Tasks { get { return _tasks; } }

    ...
}

      

I am implementing State

as follows:

public class State : IState {
    private readonly TaskChain _taskChain = new TaskChain();

    public State(Precondition precondition, Execution execution) {
        _taskChain.Tasks.Add(precondition);
        _taskChain.Tasks.Add(execution);
    }

    public bool Run() {
        return _taskChain.Run();
    }

    public IState FailureState {
        get { return (IState)_taskChain.Tasks[0].FailureTask; }
    }

    ITask ITask.FailureTask {
        get { return FailureState; }
    }
}

      

which, as you can see, uses explicit interface implementations to "hide" FailureTask

and show the property instead FailureState

.

The problem comes from the fact that I also want to define StatesChain

which inherits from IState

both and IHasStateList

(and also displays ITask

and IHasTaskList

, implemented as explicit interfaces) and I want it to hide IHasTaskList

Tasks

and show only as well IHasStateList

States

. (What is in the "Problem" section should be valid after that, but I thought it would be more convenient for the reader to put it first).

(pff..long text) Thank you!

+2


a source to share


2 answers


On the line where you get the error, you are trying to return IList<IStates>

as if it were an instance of the type IList<ITask>

. This doesn't work automatically because the two types are different (regardless of whether the shared parameters are related).

In C # 3.0 or older, there is no way to automatically achieve this. C # 4.0 adds support for covariance and contravariance, which serves exactly this purpose. But as you noticed, this only works when the returned collection is read-only. The type IList<T>

does not guarantee this, so it is not annotated as covariant in .NET 4.0.

To make this work using C # 4.0, you need to use a truly read-only type that has a covariant annotation in the structure - the best option in your case - IEnumerable<T>

(although you could define your own using out T

).

To add more details, in C # 4.0 you can declare an interface to be both covariant and inconsistent. The first case means that the compiler will let you do the conversion you want in your example (the other case is useful for write-only classes). This is done by adding explicit annotations to the interface declaration (these are already available for .NET 4.0 types). For example, the declaration IEnumerable<T>

has an annotation out

meaning it supports covariance:



public interface IEnumerable<out T> : IEnumerable { /* ... */ }

      

The compiler will now let you write:

IEnumerable<IState> states = ...
IEnumerable<ITask> tasks = states;

      

+1


a source


In short, it is not safe as read-only IList<>

does not exist (by contract). This is only an implementation that will reject entries, but this is too late because the call itself would require the interface type parameter to be both contravariant at the same time.



You could, however, return IEnumerable<>

, which is covariant in C # 4. Since this is sufficient for using LINQ, this shouldn't be too much of a disadvantage and should be better expressed as a read-only target.

+2


a source







All Articles