私はハードウェアデバイスで動作するクラスを持っています。このデバイスは多くのコマンドをサポートしており、共通のSendCommand機能を実装したいと考えています。コマンドには、入力パラメータや出力結果がある場合とない場合があります。ダウンキャストのない多型設計
私ができることは、抽象的なコマンドタイプクラスといくつかの派生コマンドタイプクラスを書くことです。これらの派生クラスは実際にはコマンドの入力/出力の仕様によって異なります。
はTask<SpecificCommandType>
、つまり派生クラスのタスクを返すようにしますが、現在のデザインではTask<BaseCommandType>
しか返すことができません。
私はシンプルなスケルトンコードを説明します:
クラス:
public abstract class BaseCommandType { ... }
public class CommandType1 : BaseCommandType {
TaskCompletionSource<CommandType1> Tcs;
}
public class CommandType2 : BaseCommandType {
TaskCompletionSource<CommandType2> Tcs;
}
My機能:
public Task<T> SendCommand<T>(BaseCommandType type) where T : BaseCommandType {
...
// if I implement TaskCompletionSource<BaseCommandType> Tcs
// in abstract class, then I can return type.Tcs.Task, and remove
// generics.
// But how can I return Task<T>?
}
私がこのようFUNC使用するために計画された
:
CommandTypeX cmd = new CommandTypeX(...);
SendCommand<CommandTypeX>(cmd).ContinueWith(t => {
// access some specifics of t.Result as CommandTypeX
});
Task<CommandTypeX>
を返すためにクラスをどのように設計すればよいですか?
または、私が必要とする(ダウンキャストなしの)良い方法がありますか?
アップデート1:
public abstract class BaseCommandType {
public TaskCompletionSource<BaseCommandType> Tcs;
}
public class CommandTypeX : BaseCommandType { }
public Task<BaseCommandType> SendCommand(BaseCommandType type) {
...
return type.Tcs.Task;
}
// when task finishes:
type.Tcs.SetResult(type); // where type is actually of CommandTypeX
// usage:
CommandTypeX cmd = new CommandTypeX(...);
SendCommand(cmd).ContinueWith(t => {
CommandTypeX command = t.Result as CommandTypeX;
if (command != null) ...
});
しかし、それはまさに私が避けたいものです: 私は意気消沈して、このようにそれを行うことができ、より正確に(?Iを行うことができますが、そうではありません)最初の場所。
更新2: 私は別の方法を見つけたと思いますが、まだよく分かりません。
public abstract class BaseCommandType {
internal abstract void SetTcs<T>(TaskCompletionSource<T> tcs);
internal abstract void HandleData(byte[] data);
}
public class CommandType1 : BaseCommandType {
private TaskCompletionSource<CommandType1> _tcs1 = new TaskCompletionSource<CommandType1>();
public string Data1;
internal override void SetTcs<T>(TaskCompletionSource<T> tcs)
{
_tcs1 = tcs as TaskCompletionSource<CommandType1>;
}
internal override void HandleData(byte[] data)
{
// Data1 = someFuncOn(data)
_tcs1.TrySetResult(this);
}
}
public class CommandType2 : BaseCommandType {
private TaskCompletionSource<CommandType2> _tcs2 = new TaskCompletionSource<CommandType2>();
public int[] Data2;
internal override void SetTcs<T>(TaskCompletionSource<T> tcs)
{
_tcs2 = tcs as TaskCompletionSource<CommandType2>;
}
internal override void HandleData(byte[] data)
{
// Data2 = someFuncOn(data)
_tcs2.TrySetResult(this);
}
}
public class Device {
private List<BaseCommandType> _commandList = new List<BaseCommandType>();
public Task<T> SendCommand<T>(T t) where T : BaseCommandType
{
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
t.SetTcs<T>(tcs);
_commandList.Add(t);
// later in other thread then device answers
// locate command in list
// BaseCommandType c = _commandList[some index];
// _commandList.RemoveAt(some index);
// c.HandleData(null);
return tcs.Task;
}
}
// usage like this:
CommandType2 command = new CommandType2();
device.SendCommand<CommandType2>(command).ContinueWith(t =>
{
CommandType2 command2 = t.Result;
// use command2.Data2 here;
});
はアップデート1でより良いこのようですか?少なくとも私はライブラリー内のキャストロジックを隠すことができるので、外部的にはすべてが型の安全で堅牢なものになります。 またはこれをさらに改善するにはどうすればよいですか?
これは共分散問題のようです。 'BaseCommandType'が動作するためのインタフェースを実装するためには' BaseCommandType'が必要です。 – BradleyDotNET