1

List<ICalculation>アプリケーションで事前定義されたすべての計算のリポジトリとして機能するジェネリックがあるとしましょう...

ICalculation<T, U>より基本的な を実装するという汎用インターフェイスがありますICalculation

public interface ICalculation
{
    string Identifier { get; }
    object Calculate(object inputData);
}

public interface ICalculation<in TIn, out TOut> : ICalculation
{
    string Identifier { get; }
    TOut Calculate(TIn inputData)
}

このインターフェイスを実装する抽象クラス CalculationBase もあります

public abstract class CalculationBase<TIn, TOut> : ICalculation<in TIn, out TOut>, ICalculation
{
    public abstract string Identifier { get; }
    public abstract Func<TIn, TOut> Calculation { get; }
    public virtual TOut Calculate(TIn inputData)
    {
        return Calculate(inputData, Calculation);
    }
    virtual object ICalculation.Calculate(object inputData)
    {
        return (TOut)calculation((TIn)inputData);
    }
    public static TOut Calculate(TIn inputData, Func<TIn, TOut> calculation)
    {
        if (calculation == null || inputData == null)
            return default(TOut);

        return calculation(inputData);
    }
}

だから、今、私はいくつかの入力に対して機能するCalculationBaseを実装する計算をたくさん持っています... 1つの例:

public sealed class NumberOfBillableInvoices : CalculationBase<IClientAccount, int>
{
    public override string identifier { get { return "@BillableInvoiceCount"; } }
    public override Func<IClientAccount, int> Calculation
    {
        get { return inputData => inputData.Invoices.Count(i => i.IsBillable); }
    }
} 

各計算は特定のタイプのオブジェクトを対象としており、計算の性質に応じて異なる出力を返します。例: 通貨の計算では小数が返される場合があり、カウンターでは整数または長整数などが返される可能性があります。

私は、アプリケーションのロード時にそれ自体をロードする計算リポジトリを持っています。数式を評価する必要がある場合、計算エンジンはクエリ対象のオブジェクトを取得します。この例では、型の具体的なインスタンスがIClientAccountあり、それに対していくつかの数式を評価したいと考えています"Math.Max(@BillableInvoiceCount - 5, 0) * $1.20"。エンジンは、TIn が IClientAccount 型であるすべての計算を実行して取得し、その計算を式で見つかったトークン (つまり@BillableInvoiceCount) と照合します。次に、NCalc、FLEE、または別の計算エンジンなどの計算エンジンが最終的な方程式を評価します。

したがって、私の問題は、正しいトークンを探してすべての計算を反復処理したくないということです。現実的には、トークンが複数のオブジェクト タイプにまたがる場合、トークンが衝突する可能性があります。たとえば、同じトークンを使用して、異なるコンテキストで異なることを意味する場合があります。リポジトリ内の計算を、TIn が計算しようとしているオブジェクト タイプと一致するものだけに絞り込むことができれば、より簡単になります。

この時点でいくつかの考えがあります -

1)。オブジェクトの TIn 部分のみをマーシャリングするリポジトリを作成できますか? これに対する答えはおそらくノーだと思います...しかし、それが可能である可能性がある場合、これを実装する方法の最初の手がかりがありません-誰かにアイデアはありますか?

2)。TIn がクエリ対象のオブジェクトのタイプと一致するすべての計算についてリポジトリにクエリを実行する方法はありますか? もしそうなら、どのように?

3)。計算対象の TIn/TOut のすべての組み合わせに基づいて、複数のリポジトリがありますか? もしそうなら、クエリを実行しているオブジェクトと正しいリポジトリを結合するにはどうすればよいですか? 私はまだTIn部分のみに基づいてリポジトリを照合しようとしているため...

4)。すべての計算で異なる型を返すのではなく double を返すようにすると、リポジトリを入力型だけに型付けして単純にすることができます...しかし、これは単純ですが、意味的には間違っているように感じます。

考え?

事前に乾杯:)

4

3 に答える 3

1

ジェネリックはコンパイル時のアーティファクトであることを忘れないでください。ジェネリックを使用する場合は、作成時にどのクラスが必要かを知る必要があります。実行時のチェックが必要な場合は、一般的でない方法がおそらく最適です。

オブジェクトが正しいタイプであることを知っていると仮定すると、オブジェクトのオーバーロードは目的に応じて正常に機能するはずです。.NET フレームワークは、マッピングが失敗した場合に例外をスローするため、サイレント エラーについて心配する必要はありません。

1)。オブジェクトの TIn 部分のみをマーシャリングするリポジトリを作成できますか? これに対する答えはおそらくノーだと思います...しかし、それが可能である可能性がある場合、これを実装する方法の最初の手がかりがありません-誰かにアイデアはありますか?

マーシャリングとは、基になるデータの変換を指します。この場合、ICalculation.Calculateメソッドが変換を行うため、生のオブジェクトを単純に渡すことができます。発生する可能性のある唯一の問題TInは、値型で null を渡す場合です (この場合、Calculatenull 処理は行われません) 。

2)。TIn がクエリ対象のオブジェクトのタイプと一致するすべての計算についてリポジトリにクエリを実行する方法はありますか? もしそうなら、どのように?

この場合、よりクリーンな例外ツリーが必要でない限り、非汎用バージョンを使用してみます。

3)。計算対象の TIn/TOut のすべての組み合わせに基づいて、複数のリポジトリがありますか? もしそうなら、クエリを実行しているオブジェクトと正しいリポジトリを結合するにはどうすればよいですか? 私はまだTIn部分のみに基づいてリポジトリを照合しようとしているため...

これを行うには、保存方法をTIn両方ではなく保存のみにするのがコツです。たとえばDictionary<Type,ICalculation>Typeは ですTIn

4)。すべての計算で異なる型を返すのではなく double を返すようにすると、リポジトリを入力型だけに型付けして単純にすることができます...しかし、これは単純ですが、意味的には間違っているように感じます。

ここで注意すべきことの 1 つは、メソッド呼び出し間で変換を行っていない場合にのみ、私の提案が機能することです。に があり、intそれobjectを に変換しようとするdoubleと失敗します。

Convert.ChangeTypeこれは、直接キャストする代わりに呼び出すことで回避できます。次のように機能します。

object ICalculation.Calculate(object inputData)
{
    if (inputData == null && typeof(TIn).IsValueType)
        return default(TOut);
    return Calculate((TIn)Convert.ChangeType(inputData, typeof(TIn));
}

メソッドのいくつかの変更点に注意してください。

  • 例外をスローするだけnullなので、値型の明示的なハンドラーを追加しました。Convert.ChangeType
  • Calculateオーバーロードされた場合に備えて、の一般的な形式を呼び出します。
  • 2 つのインターフェイスの対称性を提供するだけなので、本当に正当な理由がない限り、このメソッドをオーバーロードするべきではありません。
  • 結果を変換しません。calculationとの両方Calculateが返されることが保証されてTOutいるため、変換は冗長です。
  • which を追加すると、をChangeTypeに渡すことを静かに処理できます。intdecimal

には危険があることに注意してくださいChangeType。明示的なキャストに似ています。データに何が起こっても、変換を行うために最善を尽くします。オーバーフローは期待どおりに処理されるように見えますが、切り捨ては静かに行われます。

主なポイントは、そのようなものがある場合は、エッジケースをテストすることです。

于 2013-03-29T19:17:59.947 に答える
1

すべてが から派生していることがわかっている場合はCalculationBase<,>、次のことができると思います。

// can also be made an extension method
static bool HasCorrectInType(ICalculation calc, Type desiredInType)
{
  var t = calc.GetType();
  do
  {
    if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(CalculationBase<,>))
      return t.GetGenericArguments()[0].IsAssignableFrom(desiredInType);
    t = t.BaseType;
  } while (t != null)

  throw new Exception("The type " + calc.GetType() + " not supported");
}

次に、次のように使用します。

List<ICalculation> repository = XXX;
var matches = repository.Where(c => HasCorrectInType(c, type));

編集:新しいアイデア:新しいプロパティを配置する場合:

public Type InType
{
  get { return typeof(TIn); }
}

抽象クラスCalculationBase<TIn, TOut>に追加し、このプロパティを非ジェネリック インターフェイスに追加するとICalculation、基本クラスを反復処理する必要はなくなりますが、calc.InType.IsAssignableFrom(desiredInType)直接言うことができます。

于 2013-03-29T18:24:54.027 に答える
0

最も簡単な解決策を選択する必要があると思います。リフレクションを使用して特定の型を取得するか、すべての場合に double を返すことができます。集中的なデータ処理の場合、特定の型を見つけようとすると速度が低下する可能性があるため、double または integer を返すことはまったく問題ありません。

于 2013-03-29T18:20:56.940 に答える