Проблема иерархии класса (с дисперсией универсального рода!)

StackOverflow https://stackoverflow.com/questions/2717684

Вопрос

Эта проблема:

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>.
    }
}

Предполагая IList Вернулся будет только чтение-только, я знаю, что то, что я пытаюсь достичь, это безопасно (или не так ли?). Есть ли способ, которым я могу достичь того, что я пытаюсь? Я не хотел бы пытаться реализовать себя TasksChain Алгоритм (опять же!), Как это будет подвержено ошибке и приведет к дублированию кода. Может быть, я мог бы просто определить абстрактную цепочку, а затем реализовать оба TasksChain а также StatesChain оттуда? Или, возможно, внедрение Chain<T> сорт?

Как бы вы подошли к этой ситуации?

Детали:Я определил ITask интерфейс:

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

и а IState Интерфейс, который наследует от ITask:

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

Я также определил IHasTasksList интерфейс:

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

и ан IHasStatesList:

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

Теперь я определил TasksChain, это класс, который имеет какую-то логику кода, который будет манипулировать цепочкой задач (остерегайтесь, что TasksChain сама собой своего рода ITask!):

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

    ...

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

    ...
}

Я реализую State следующий путь:

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; }
    }
}

Что, как вы видите, использует явные реализации интерфейса для «скрытия» FailureTask и вместо этого шоу FailureState имущество.

Проблема исходит от того, что я также хочу определить StatesChain, что наследует как из IState а также IHasStateList (а это также реализуется ITask а также IHasTaskList, реализован как явные интерфейсы), и я хочу, чтобы он также скрылся IHasTaskListС. Tasks и только шоу IHasStateListС. States. (Что содержится в разделе «Проблема», необходимо по-настоящему быть после этого, но я подумал, что это сначала будет более читательным для читателя).

(PFF..Long Text) Спасибо!

Это было полезно?

Решение

На линии, где вы получаете ошибку, вы пытаетесь вернуться IList<IStates> Как будто это был случай типа IList<ITask>. Отказ Это не работает автоматически, потому что два типа различны (независимо от того, чтобы общие параметры связаны).

В C # 3,0 или старше, нет возможности достичь этого автоматически. C # 4.0 добавляет поддержку ковариации и контравариации, что именно именно эта цель служит. Но, как вы отмечали, это работает только тогда, когда возвращенная коллекция является только для чтения. То IList<T> Тип не гарантирует, что, поэтому он не аннотируется как ковариант в .NET 4.0.

Чтобы сделать эту работу, используя C # 4.0, вам нужно будет использовать Действительно только чтение типа, который имеет ковариантную аннотацию в рамках - лучший вариант в вашем случае IEnumerable<T> (хотя вы можете определить свой собственный, используя out T модификатор).

Чтобы добавить больше деталей, в C # 4.0 вы можете объявить интерфейс как ковариантный или CONTRAINT. Первый случай означает, что компилятор позволит вам выполнить требуемое преобразование в вашем примере (другой случай полезен для классов только для записи). Это делается путем добавления явных аннотаций к объявлению интерфейса (они уже доступны для типов .NET 4.0). Например, декларация IEnumerable<T> имеет out Аннотация означает, что он поддерживает ковариацию:

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

Теперь компилятор позволит вам написать:

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

Другие советы

Короче, нет, это не безопасно, так как «только для чтения» IList<> не существует (контракт-мудрый). Это только реализация, которая откажется от записи, но это слишком поздно, поскольку сам звонок потребует, потребует параметра типа интерфейса как совпадающим одновременно.

Вы могли бы, однако, вернуть IEnumerable<> Вместо этого, который является ковариантным в C # 4. Так как этого достаточно использовать LINQ, что не должно быть слишком большим недостатком и выражает природу только для чтения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top