Проблема иерархии класса (с дисперсией универсального рода!)
-
01-10-2019 - |
Вопрос
Эта проблема:
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, что не должно быть слишком большим недостатком и выражает природу только для чтения.