3

特定のタイプのオブジェクトを 2 つの異なるタイプとして使用できるようにしたい状況があります。「基本」型の 1 つがインターフェイスである場合、これは問題になりませんが、私の場合は、両方とも具象型であることが望ましいです。

基本型の 1 つのメソッドとプロパティのコピーを派生型に追加し、派生型からその基本型への暗黙的な変換を追加することを検討しています。その後、ユーザーは、複製されたメソッドを直接使用するか、基本型の変数に代入するか、基本型を取るメソッドに渡すことにより、派生型を基本型として扱うことができます。

このソリューションは私のニーズによく合っているようですが、何か足りないものはありますか? これが機能しない状況や、API を使用する際に単純化する代わりに混乱を招く可能性がある状況はありますか?

編集:私の特定のシナリオの詳細:

これは、自動化されたトレーディング システム開発環境であるRightEdgeでインディケータが記述される方法の潜在的な将来の再設計のためのものです。価格データは、一定期間 (1 分、1 日など) の始値、安値、高値、終値の値を持つ一連のバーとして表されます。指標は、一連のデータに対して計算を実行します。単純なインジケーターの例は移動平均インジケーターです。これは、入力の最新のn 個の値の移動平均を示します。ここで、 nはユーザー指定です。移動平均は足の終値に適用されるか、別のインジケーターの出力に適用されて平滑化されます。

新しいバーが入るたびに、インジケーターはそのバーの出力の新しい値を計算します。

ほとんどのインディケータには 1 つの出力シリーズしかありませんが、複数の出力があると便利な場合があり ( MACDを参照)、これをサポートしたいと考えています。

したがって、インジケーターは、新しいデータが入ったときに呼び出されるメソッドを持つ「コンポーネント」クラスから派生する必要があります。ただし、出力シリーズが 1 つしかないインジケーターの場合 (これがほとんどです)、それはそれらにとって良いことです。シリーズ自体として行動する。そうすれば、ユーザーは を使用するSMA.Current代わりに、SMA の現在の値に を使用できますSMA.Output.Current。同様に、 よりも のIndicator2.Input = Indicator1;方が望ましいIndicator2.Input = Indicator1.Output;です。これは大した違いではないように思えるかもしれませんが、ターゲット ユーザーの多くはプロの .NET 開発者ではないため、できるだけ簡単にしたいと考えています。

私の考えは、出力系列が 1 つしかないインジケータに対して、インジケータからその出力系列への暗黙的な変換を行うことです。

4

6 に答える 6

4

あなたはあまりにも多くの詳細を提供していないので、あなたが提供したものから回答しようとしています.

基本的な違いを見てみましょう: 基本
Bと派生型Dがある場合、割り当ては次のようになります。

B my_B_object = my_D_object;

同じオブジェクトへの参照を割り当てます。一方、BDが独立した型であり、それらの間で暗黙的な変換が行われる場合、上記の代入はのコピーを作成し、それ (またはがクラスのmy_D_object場合はそれへの参照) を に格納します。Bmy_B_object

要約すると、「実際の」継承は参照によって機能し (参照への変更は多くの参照によって共有されるオブジェクトに影響します)、カスタム型変換は一般に値によって機能します (実装方法によって異なりますが、「参照によって」に近いものを実装します)。 " コンバーターの動作はほとんど正気ではありません): 各参照は独自のオブジェクトを指します。

インターフェイスを使いたくないとおっしゃっていますが、なぜですか?コンボ インターフェイス + ヘルパー クラス + 拡張メソッド (C# 3.0 および .Net 3.5 以降が必要) を使用すると、実際の多重継承にかなり近づけることができます。これを見てください:

interface MyType { ... }
static class MyTypeHelper {
    public static void MyMethod(this MyType value) {...}
}

「ベース」タイプごとにこれを行うと、必要なメソッドにデフォルトの実装を提供できます。

これらはそのままでは仮想メソッドとして動作しません。しかし、それを達成するためにリフレクションを使用することができます。Helper クラスの実装内から次のことを行う必要があります。

  1. で取得System.Typeするvalue.GetType()
  2. その型に署名に一致するメソッドがあるかどうかを調べる
  3. 一致するメソッドが見つかった場合は、それを呼び出して戻ります (ヘルパーの残りのメソッドは実行されません)。
  4. 最後に、特定の実装が見つからない場合は、残りのメソッドを実行して、「基本クラスの実装」として機能させます。

C# の多重継承。ただし、これをサポートする基本クラスに醜いコードが必要になるという唯一の注意点と、リフレクションによるオーバーヘッドがあります。ただし、アプリケーションが大きなプレッシャーの下で動作していない限り、これでうまくいくはずです。

繰り返しになりますが、なぜインターフェースを使いたくないのでしょうか? メソッドの実装を提供できないことが唯一の理由である場合は、上記のトリックで解決できます。インターフェースに他の問題がある場合は、それらを整理しようとするかもしれませんが、最初にそれらについて知る必要があります;)

お役に立てれば。


[編集:コメントに基づく追加]

元の質問に詳細を追加しました。インターフェイスを使用したくないのは、ユーザーがインターフェイスを誤って実装したり、インジケーターを実装したい場合にオーバーライドする必要があるメソッド (つまり NewBar) を誤って呼び出したりして自分自身を撃ってしまうのを防ぎたいからです。直接呼び出す必要はありません。

私はあなたの更新された質問を見てきましたが、コメントはそれをかなり要約しています。多分私は何かが欠けているかもしれませんが、インターフェイス + 拡張機能 + リフレクションは、多重継承が可能なすべてを解決でき、タスクでの暗黙的な変換よりもはるかに優れています:

  • 仮想メソッドの動作 (実装が提供され、継承者はオーバーライドできます): ヘルパーにメソッドを含めます (上記のリフレクション「仮想化」にラップされます)。インターフェイスで宣言しないでください。
  • 抽象メソッドの動作 (実装は提供されず、継承者は実装する必要があります): インターフェイスでメソッドを宣言し、ヘルパーに含めないでください。
  • 非仮想メソッドの動作 (実装が提供され、継承者は非表示にすることができますが、オーバーライドすることはできません): ヘルパーで通常どおり実装するだけです。
  • ボーナス: 奇妙なメソッド (実装が提供されますが、継承者はとにかく実装する必要があります。基本実装を明示的に呼び出すことができます): 通常の継承または多重継承では実行できませんが、完全を期すために含めています:ヘルパーで実装を提供し、インターフェイスで宣言します。それがどのように機能するか(仮想対非仮想の側面で)、またはそれがどのように使用されるかはわかりませんが、私のソリューションはすでに多重継承を打ち負かしています:P

注: 非仮想メソッドの場合、基本実装が確実に使用されるように、インターフェイスの型を「宣言された」型にする必要があります。これは、継承者がメソッドを非表示にする場合とまったく同じです。

ユーザーが誤って実装して足を撃たれるのを防ぎたい

ここでは、非仮想 (ヘルパーでのみ実装) が最適に機能するようです。

または、インジケーターを実装したい場合にオーバーライドする必要があるメソッド (つまり、NewBar) を誤って呼び出す

それが、抽象メソッド (または一種の超抽象的なものであるインターフェース) が最も輝くところです。継承者はメソッドを実装する必要があります。そうしないと、コードはコンパイルされません。場合によっては、仮想メソッドが適している場合があります (一般的な基本実装があるが、より具体的な実装が妥当な場合)。

ただし、直接呼び出す必要はありません

メソッド (またはその他のメンバー) がクライアント コードに公開されているが、クライアント コードから呼び出すべきではない場合、それを強制するプログラムによる解決策はありません (実際にはありますが、我慢してください)。ドキュメントに記載されている適切な場所に対処してください。APIを文書化しているからですよね?;) ここでは、変換も多重継承も役に立ちません。ただし、リフレクションが役立つ場合があります。

if(System.Reflection.Assembly.GetCallingAssembly()!=System.Reflection.Assembly.GetExecutingAssembly())
    throw new Exception("Don't call me. Don't call me!. DON'T CALL ME!!!");

using System.Reflection;もちろん、ファイルにステートメントがある場合は、それを短くすることができます. そして、ところで、例外のタイプとメッセージをよりわかりやすいものに自由に変更してください;)。

于 2010-04-18T18:57:50.437 に答える
1

あなたのメソッドがクロスキャストをサポートするようには聞こえません。真の多重継承はそうするでしょう。

多重継承を持つC++の例:

class A {};
class B {};
class C : public A, public B {};

C o;
B* pB = &o;
A* pA = dynamic_cast<A*>(pB); // with true MI, this succeeds
于 2010-04-18T18:44:40.043 に答える
1

2 つの問題があります。

  • ユーザー定義の型変換演算子は、一般にあまり見つけられません。IntelliSense には表示されません。

  • 暗黙的なユーザー定義の型変換演算子では、演算子がいつ適用されるかが明確でないことがよくあります。

これは、型変換演算子をまったく定義してはならないということではありませんが、ソリューションを設計するときはこれを念頭に置く必要があります。

簡単に見つけて認識できる解決策は、明示的な変換メソッドを定義することです。

class Person { }

abstract class Student : Person
{
    public abstract decimal Wage { get; }
}

abstract class Musician : Person
{
    public abstract decimal Wage { get; }
}

class StudentMusician : Person
{
    public decimal MusicianWage { get { return 10; } }

    public decimal StudentWage { get { return 8; } }

    public Musician AsMusician() { return new MusicianFacade(this); }

    public Student AsStudent() { return new StudentFacade(this); }
}

使用法:

void PayMusician(Musician musician) { GiveMoney(musician, musician.Wage); }

void PayStudent(Student student) { GiveMoney(student, student.Wage); }

StudentMusician alice;
PayStudent(alice.AsStudent());
于 2010-04-18T18:43:05.210 に答える
0

たぶん私はこれで行き過ぎているかもしれませんが、あなたのユースケースは、Rx ( Rx in 15 Minutes ) を構築することで大きな利益を得ることができるかのように疑わしく聞こえます。

Rx は、値を生成するオブジェクトを操作するためのフレームワークです。このようなオブジェクトを非常に表現力豊かな方法で構成し、生成された値のストリームを変換、フィルタリング、および集約することができます。

あなたはバーを持っていると言います:

class Bar
{
    double Open { get; }
    double Low { get; }
    double High { get; }
    double Close { get; }
}

シリーズは、バーを生成するオブジェクトです。

class Series : IObservable<Bar>
{
    // ...
}

移動平均インジケーターは、新しいバーが生成されるたびに最後のカウントバーの平均を生成するオブジェクトです。

static class IndicatorExtensions
{
    public static IObservable<double> MovingAverage(
        this IObservable<Bar> source,
        int count)
    {
        // ...
    }
}

使用法は次のようになります。

Series series = GetSeries();

series.MovingAverage(20).Subscribe(average =>
{
    txtCurrentAverage.Text = average.ToString();
});

複数の出力を持つインジケーターは、GroupBy に似ています。

于 2010-04-18T19:57:34.200 に答える
0

その後、ユーザーは、複製されたメソッドを直接使用するか、基本型の変数に代入するか、基本型を取るメソッドに渡すことにより、派生型を基本型として扱うことができます。

ただし、これは異なる動作をします。継承の場合、オブジェクトを渡すだけです。ただし、暗黙的なコンバーターを実装することにより、変換が行われるときに常に新しいオブジェクトを構築することになります。2 つのケースでは動作がまったく異なるため、これは非常に予想外の場合があります。

個人的には、これを新しい型を返すメソッドにします。実際の実装がエンド ユーザーに明らかになるからです。

于 2010-04-18T18:41:26.853 に答える
0

これはばかげた考えかもしれませんが、設計に複数の継承が必要な場合は、単純に MI で言語を使用しないのはなぜですか? 多重継承をサポートする .NET 言語がいくつかあります。頭のてっぺんから:エッフェル、パイソン、アイオケ。おそらくもっとあります。

于 2010-04-19T00:24:14.027 に答える