2017-07-26 7 views
0

私はハードウェアデバイスで動作するクラスを持っています。このデバイスは多くのコマンドをサポートしており、共通の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でより良いこのようですか?少なくとも私はライブラリー内のキャストロジックを隠すことができるので、外部的にはすべてが型の安全で堅牢なものになります。 またはこれをさらに改善するにはどうすればよいですか?

+0

これは共分散問題のようです。 'BaseCommandType'が動作するためのインタフェースを実装するためには' BaseCommandType'が必要です。 – BradleyDotNET

答えて

0

私は

public abstract class BaseCommandType<T> 
{ 
    public abstract TaskCompletionSource<T> Tcs { get; } 
} 
public class CommandType1 : BaseCommandType<CommandType1> 
{ 
} 
public class CommandType2 : BaseCommandType<CommandType2> 
{ 
} 


public Task<T> SendCommand<T>(T type) where T : BaseCommandType<T> 
{ 
    return type.Tcs.Task; 
} 

EDIT私は右のそれを理解しているかどうかわからないんだけど、...:あなたは、一般的な入力パラメータを持つことができない場合は、代わりに抽象beahviorを定義ダウンキャストのよりも、

BaseCommandTypeは、ContinueWithメソッドで呼び出して、コマンドでオーバーライドします。入力タイプがわからない場合は、それを汎用にすることはできません。私はあなたの質問については理解していない

+0

残念ながら、私は基本的なクラスではなく、内部目的のための基本クラスまたはインタフェースとしての '型'パラメータに固執すべきです。これは、これらの派生クラスインスタンスを共通リストで使用し、オーバーロードされたメソッドを使用してSendCommandの目標を達成するためです。 – lilo0

+0

私ができることはあまりありません。私の編集を見てください。 –

+0

まあ、間違っているかもしれません。私はgeneric型のT型として 'type'パラメータを作ることができますが、型T generic型を作ることはできません。だから、私は基本抽象クラスはジェネリックではないと思う。 'タイプ'の唯一の主要な要件は、List に追加できるはずです。 BaseCommandType ベースクラスでは、私はそうしないでしょう。 – lilo0

0

もの:

  1. なぜSendCommand<T>(BaseCommandType)のためのパラメータの型に制限?代わりにその方法をSendCommand<T>(T)にすれば何がうまくいかないでしょうか?
  2. なぜあなたはContinueWith()を使用していますか? awaitはより簡単で表現力豊かです。一瞬のものを無視

、私はあなたがこのような何かを行うことができるはず期待:

public async Task<T> SendCommand<T>(BaseCommandType type) where T : BaseCommandType 
{ 
    return await type.Tcs.Task as T; 
} 

個人的に、私はすべてのジェネリックを行くだろう。しかし、何らかの理由でそれができないと言っているので、いつかキャストする必要があります。その制約ではキャストを単純に回避することはできません。上記は、これを行う最も簡単で最も有用なアプローチのように思えます。

上記の方法であなたのニーズに対応できない場合は、設計上の制約と要件を明確に示した良いMinimal, Complete, and Verifiable code exampleを含めることで問題を改善してください。ゴール。

+0

まず、SendCommand (Tタイプ) 'は、Tがそれ自体がgenericでない限り、okです。第二に、私は.Net 4.0をターゲットにしているので、私は待っていませんでした。したがって、私はあなたのコードをチェックすることはできませんが、もし私が 'return type.Tcs.Task as Task ;'を実行すると、NullReferenceExceptionがスローされ、キャストに失敗します。 'type'制約のための_Jan Muncinsky_答えに対する私のコメントを見てください。 – lilo0

+0

_ "Tがジェネリック自身でない限り" _ - それは何と関係がありますか? _ "NullReferenceExceptionがスローされました" _ - それが何を意味するかわかりません。ここには実際のコードはありませんので、どのような例外がスローされるのか分かりません。 [mcve]のコンテキストでのみ起こることがあります。それ以外では、最初に 'Tcs'の値を初期化していない限り、' type.Tcs.Task as Task 'という式に' NullReferenceException'がスローされることはありません。 –

関連する問題