12

ifさまざまなタイプのクラスを処理するために、いくつかの成長する構造を処理するより良い方法を見つけようとしています。これらのクラスは、最終的には、いくつかの追加の状態情報を持つ、異なる値の型 (int、DateTime など) のラッパーです。したがって、これらのクラスの主な違いは、含まれるデータのタイプです。これらはジェネリック インターフェイスを実装していますが、同種のコレクションに保持する必要があるため、非ジェネリック インターフェイスも実装しています。クラス インスタンスは、それらが表すデータのタイプに従って処理され、その伝播はそれに基づいて継続または継続されません。

これは必ずしも .NET または C# の問題ではありませんが、私のコードは C# です。

クラスの例:

interface ITimedValue {
 TimeSpan TimeStamp { get; }
}

interface ITimedValue<T> : ITimedValue {
 T Value { get; }
}

class NumericValue : ITimedValue<float> {
 public TimeSpan TimeStamp { get; private set; }
 public float Value { get; private set; }
}

class DateTimeValue : ITimedValue<DateTime> {
 public TimeSpan TimeStamp { get; private set; }
 public DateTime Value { get; private set; }
}

class NumericEvaluator {
 public void Evaluate(IEnumerable<ITimedValue> values) ...
}

私は2つのオプションを考え出しました:

ダブルディスパッチ

私は最近、訪問者パターンと、そのようなケースを処理するための二重ディスパッチの使用について学びました。望ましくないデータが伝播されないようにするため、これは魅力的です (int のみを処理したい場合は、DateTime とは異なる方法で処理できます)。また、さまざまなタイプの処理方法の動作は、ディスパッチを処理する単一のクラスに限定されます。ただし、新しい値の型をサポートする必要がある場合は、かなりのメンテナンスが必要です。

ユニオンクラス

サポートされている各値型のプロパティを含むクラスは、これらの各クラスが格納するものになる可能性があります。値に対する操作は、適切なコンポーネントに影響します。これは、ダブル ディスパッチ戦略よりも複雑ではなく、メンテナンスも少なくて済みますが、「私はそのデータ型を操作しない」という行に沿って区別できなくなるため、すべてのデータが不必要に伝播することを意味します。 "。ただし、新しい型をサポートする必要がある場合は、このクラスに入れるだけで済みます (さらに、新しいデータ型をサポートするために作成する必要がある追加のクラスはすべて)。

class UnionData {
 public int NumericValue;
 public DateTime DateTimeValue;
}

より良いオプションはありますか?これら 2 つのオプションのいずれかに、私がすべきだとは考えていなかった何かがありますか?

4

4 に答える 4

3

方法 1、二重ディスパッチに動的を使用 (クレジットはhttp://blogs.msdn.com/b/curth/archive/2008/11/15/c-dynamic-and-multiple-dispatch.aspxに移動)。基本的に、次のように訪問者パターンを単純化できます。

class Evaluator {
 public void Evaluate(IEnumerable<ITimedValue> values) {
    foreach(var v in values)
    {
        Eval((dynamic)(v));
    }
 }

 private void Eval(DateTimeValue d) {
    Console.WriteLine(d.Value.ToString() + " is a datetime");
 }

 private void Eval(NumericValue f) {
    Console.WriteLine(f.Value.ToString() + " is a float");
 }

}

使用例:

var l = new List<ITimedValue>(){
    new NumericValue(){Value= 5.1F}, 
    new DateTimeValue() {Value= DateTime.Now}};

new Evaluator()
    .Evaluate(l);
       // output:
       // 5,1 is a float
       // 29/02/2012 19:15:16 is a datetime

方法 2 は、ここで@Juliet によって提案されているように、C# で Union 型を使用します(別の実装はこちら) 。

于 2012-02-29T18:20:06.430 に答える
0

同様の状況を解決したことをお伝えします - TicksDateTime または TimeSpan を double としてコレクションに格納し、型パラメーターの where 制約として IComparable を使用することです。double から double への変換は、ヘルパー クラスによって実行されます。

この前の質問を参照してください。

おかしなことに、これはボックス化やボックス化解除などの他の問題につながります。私が取り組んでいるアプリケーションは非常に高いパフォーマンスを必要とするため、ボクシングを避ける必要があります。さまざまなデータ型 (DateTime を含む) を一般的に処理する優れた方法を思いつくことができれば、私はすべて耳にします!

于 2012-02-29T16:41:32.787 に答える
0

実際に必要なインターフェイスを実装し、実装する型が値を定義できるようにしないのはなぜですか? 例えば:

class NumericValue : ITimedValue<float> {
 public TimeSpan TimeStamp { get; private set; }
 public float Value { get; private set; }
}

class DateTimeValue : ITimedValue<DateTime>, ITimedValue<float> {
 public TimeSpan TimeStamp { get; private set; }
 public DateTime Value { get; private set; }
 public Float ITimedValue<Float>.Value { get { return 0; } }
}

class NumericEvaluator {
 public void Evaluate(IEnumerable<ITimedValue<float>> values) ...
}

DateTime 実装の動作を特定の使用法 (Evaluate 関数の代替実装など) に基づいて変更したい場合は、定義上、ITimedValue<DateTime>. たとえば、 1 つ以上のConverterデリゲートを提供することで、静的に型付けされた適切なソリューションを得ることができます。

最後に、本当に NumericValue インスタンスのみを処理したい場合は、NumericValue インスタンスではないものを除外します。

class NumericEvaluator {
    public void Evaluate(IEnumerable<ITimedValue> values) {
        foreach (NumericValue value in values.OfType<NumericValue>()) {
            ....
        }
    }
}
于 2012-02-29T17:13:05.223 に答える
0

良い質問。最初に頭に浮かんだのは、リフレクティブ ストラテジー アルゴリズムでした。ランタイムは、参照を保持するために使用している変数の型に関係なく、参照の最も派生した型を静的または動的に通知できます。ただし、残念ながら、派生型に基づいてオーバーロードが自動的に選択されるわけではなく、変数の型のみが選択されます。そのため、実行時に真の型を確認し、それに基づいて特定のオーバーロードを手動で選択する必要があります。リフレクションを使用すると、特定のサブタイプを処理するものとして識別されたメソッドのコレクションを動的に構築し、そのジェネリック型の参照を調べて、それに基づいて辞書で実装を検索できます。

public interface ITimedValueEvaluator
{
   void Evaluate(ITimedValue value);
}

public interface ITimedValueEvaluator<T>:ITimedValueEvaluator
{
   void Evaluate(ITimedValue<T> value);
}

//each implementation is responsible for implementing both interfaces' methods,
//much like implementing IEnumerable<> requires implementing IEnumerable
class NumericEvaluator: ITimedValueEvaluator<int> ...

class DateTimeEvaluator: ITimedValueEvaluator<DateTime> ...

public class Evaluator
{
   private Dictionary<Type, ITimedValueEvaluator> Implementations;

   public Evaluator()
   {
      //find all implementations of ITimedValueEvaluator, instantiate one of each
      //and store in a Dictionary
      Implementations = (from t in Assembly.GetCurrentAssembly().GetTypes()
      where t.IsAssignableFrom(typeof(ITimedValueEvaluator<>)
      and !t.IsInterface
      select new KeyValuePair<Type, ITimedValueEvaluator>(t.GetGenericArguments()[0], (ITimedValueEvaluator)Activator.CreateInstance(t)))
      .ToDictionary(kvp=>kvp.Key, kvp=>kvp.Value);      
   }

   public void Evaluate(ITimedValue value)
   {
      //find the ITimedValue's true type's GTA, and look up the implementation
      var genType = value.GetType().GetGenericArguments()[0];

      //Since we're passing a reference to the base ITimedValue interface,
      //we will call the Evaluate overload from the base ITimedValueEvaluator interface,
      //and each implementation should cast value to the correct generic type.
      Implementations[genType].Evaluate(value);
   }   

   public void Evaluate(IEnumerable<ITimedValue> values)
   {
      foreach(var value in values) Evaluate(value);
   }
}

メインの Evaluator だけが IEnumerable を処理できることに注意してください。各 ITimedValueEvaluator 実装は、値を 1 つずつ処理する必要があります。これが実行できない場合 (たとえば、特定の型のすべての値を考慮する必要がある場合)、これは非常に簡単です。Dictionary 内のすべての実装をループして完全な IEnumerable を渡し、OfType() Linq メソッドを使用して特定のクローズ ジェネリック型のオブジェクトのみにリストをフィルター処理します。これには、リストで見つけたすべての ITimedValueEvaluator 実装を実行する必要があります。これは、リストに特定のタイプの項目がない場合は無駄な作業です。

これの優れた点は拡張性です。ITimedValue の新しいジェネリック クロージャをサポートするには、同じ型の ITimedValueEvaluator の新しい実装を追加するだけです。Evaluator クラスはそれを見つけ、コピーをインスタンス化し、それを使用します。ほとんどのリフレクティブ アルゴリズムと同様に低速ですが、実際のリフレクティブ部分は 1 回限りの処理です。

于 2012-02-29T17:18:24.017 に答える