クラス階層の問題(ジェネリックの差異を伴う!)
-
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
そこから?または、aを実装するかもしれません Chain<T>
クラス?
この状況にどのようにアプローチしますか?
詳細:私は定義しました ITask
インターフェース:
public interface ITask {
bool Run();
ITask FailureTask { get; }
}
そしてa IState
継承するインターフェイス ITask
:
public interface IState : ITask {
IState FailureState { get; }
}
私も定義しました IHasTasksList
インターフェース:
interface IHasTasksList {
List<Tasks> Tasks { get; }
}
と IHasStatesList
:
interface IHasTasksList {
List<Tasks> States { get; }
}
今、私はaを定義しました TasksChain
, 、それは一連のタスクを操作するコードロジックを持っているクラスです(それに注意してください TasksChain
それ自体が一種です ITask
!):
class TasksChain : ITask, IHasTasksList {
IList<ITask> tasks = new List<ITask>();
...
public List<ITask> Tasks { get { return _tasks; } }
...
}
私はaを実装しています 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
's Tasks
そして、のみ表示します IHasStateList
's States
. (「問題」セクションに含まれるものは本当にこの後になるはずですが、最初に置くことはもっと読者に優しいと思いました)。
(pff..long text) ありがとう!
解決
エラーが発生した行では、返品しようとしています IList<IStates>
それがタイプのインスタンスであるかのように IList<ITask>
. 。 2つのタイプが異なるため、これは自動的に機能しません(一般的なパラメーターが関連していても)。
C#3.0以降では、それを自動的に達成する方法はありません。 C#4.0は、共分散と矛盾のサポートを追加します。これは、まさにこの目的に役立ちます。しかし、あなたが述べたように、これは返されたコレクションが読み取り専用である場合にのみ機能します。 IList<T>
タイプはそれを保証しないので、そのように注釈はありません 共変動 .NET 4.0で。
C#4.0を使用してこの作業を行うには、使用する必要があります 本当に読み取り専用タイプ, 、フレームワークに共変動の注釈があります - あなたの場合の最良の選択肢は IEnumerable<T>
(ただし、を使用して独自の定義できます out T
修飾子)。
詳細を追加するには、C#4.0で、インターフェイスを共変量または対照的なバリアントとして宣言できます。最初のケースは、コンパイラが例で必要な変換を実行できることを意味します(他のケースは書き込みのみのクラスに役立ちます)。これは、インターフェイス宣言に明示的な注釈を追加することによって行われます(これらは.NET 4.0タイプで既に利用可能です)。たとえば、の宣言 IEnumerable<T>
持っています out
注釈は、共分散をサポートすることを意味します。
public interface IEnumerable<out T> : IEnumerable { /* ... */ }
これで、コンパイラは次のように書くことができます。
IEnumerable<IState> states = ...
IEnumerable<ITask> tasks = states;
他のヒント
要するに、「読み取り専用」なので、安全ではありません。 IList<>
存在しません(契約的に)。エントリを拒否するのは実装のみですが、コール自体ではインターフェイスタイプパラメーターが同時に共同および矛盾の両方であることを要求するため、それは手遅れです。
ただし、返品できます IEnumerable<>
代わりに、C#4で共変動します。これはLINQを使用するのに十分であるため、それはあまり欠点ではないはずであり、読み取り専用の性質をより良く表現します。