意味のある演算子のオーバーロードの例[終了]


12

C#を学習しているときに、C#は演算子のオーバーロードをサポートしていることがわかりました。私は良い例に問題があります:

  1. 意味をなす(例:羊と牛という名前のクラスを追加する)
  2. 2つの文字列の連結の例ではありません

基本クラスライブラリの例は大歓迎です。


10
「センス」を定義してください!真剣に、この点をめぐる苦い熱烈な議論は、まさにこれについて大きな意見の相違があることを示しています。多くの当局は、まったく予期しないことをするように仕向けることができるため、過負荷のオペレーターを拒否します。他の人は、メソッド名も同様に完全に直感的でないように選択できると答えていますが、名前付きコードブロックを拒否する理由はありません!一般的に理にかなっていると思われる例はほとんどありません。あなたにとって賢明な例-多分。
キリアンフォス

@KilianFothに完全に同意します。最終的に、コンパイルするプログラムはコンパイラにとって意味があります。しかし、==乗算を行うために過負荷になる場合、それは私には理にかなっていますが、他の人には意味がないかもしれません!この質問は、どの施設のプログラミング言語の正当性についての質問ですか、それとも「コーディングのベストプラクティス」について話しているのですか?
ディパンメタ

回答:


27

適切な演算子のオーバーロードの明らかな例は、数値が動作するのと同じように動作するクラスです。だから、のBigIntクラス(とJalaynが示唆)、複素数行列クラス(とSuperbestが示唆)、すべての普通の数字がそうしながら、算術演算子に本当にうまくマッピングされているのと同じ操作をしていた時間によって示唆されているように(操作svickを)サブセットにうまくマップそれらの操作の。

少し抽象的には、演算子をセットのような操作を実行するときに使用operator+できるため、ユニオン補数などにoperator-なる可能性があります。これは、特に、 t 可換、期待どおりに。

C#自体には、非数値演算子のオーバーロードの優れた例があります。を使用+=-=て、デリゲートを追加および削除します。つまり、デリゲートを登録および登録解除します。+=and -=演算子は期待どおりに機能するため、これはうまく機能します。これにより、はるかに簡潔なコードが作成されます。

純粋主義者にとって、文字列+演算子の問題の1つは、それが可換でないことです。"a"+"b"はと同じではありません"b"+"a"。この例外は文字列について非常に一般的であるため理解していますがoperator+、他の型での使用が可換かどうかをどのように判断できますか?オブジェクトが文字列のようなものでない限り、ほとんどの人はそうだと思いますで実際に人々が何を仮定するかは決してわかりません。

文字列と同様に、行列の脆弱性もよく知られています。がMatrix operator* (double, Matrix)スカラー乗算であることは明らかですがMatrix operator* (Matrix, Matrix)行列乗算であることは明らかですインスタンス(すなわち、A行列ドット積乗算)。

同様に、デリゲートでの演算子の使用は明らかに数学からかなり離れているため、これらの間違いを犯す可能性は低いです。

ちなみに、2011年のACCU会議でロジャーオアスティーブラブは、他のオブジェクトよりも平等であるオブジェクトについてのセッションを発表しました。彼らのスライドは、Richard Harrisの浮動小数点の平等に関する付録と同様にダウンロードできます。概要:、ここではドラゴンに注意してくださいoperator==

演算子のオーバーロードは非常に強力なセマンティック手法ですが、使いすぎることは簡単です。理想的には、オーバーロードされた演算子の効果がコンテキストから非常に明確な状況でのみ使用する必要があります。多くの点でa.union(b)より明確でa+bあり、a*bある非常に多くの無名のよりa.cartesianProduct(b)デカルト積の結果は次のようになり、特に以来、SetLike<Tuple<T,T>>ではなくASetLike<T>

オペレーターのオーバーロードに関する実際の問題は、プログラマーがクラスが1つの方法で動作すると想定しているが、実際には別の方法で動作する場合に発生します。この種のセマンティッククラッシュは、回避しようとすることが重要であることを示唆しています。


1
あなたは、マトリックス上の演算子は本当にうまくマッピングすると言いますが、マトリックス乗算も可換ではありません。また、代理人のオペレーターはさらに強力です。d1 + d2同じタイプの2つのデリゲートに対して行うことができます。
-svick

1
@Mark:「ドット積」はベクトルでのみ定義されます。2つの行列の乗算は、単に「行列の乗算」と呼ばれます。区別は単なる意味論以上のものです。ドット積はスカラーを返しますが、matrix-multiplicationはマトリックスを返します(ちなみに、非可換です)
BlueRaja-ダニーPflughoeft

26

誰もBCLの興味深いケースの1つに言及していないことに驚いています:DateTimeTimeSpan。あなたはできる:

  • 2を加算または減算TimeSpanして別のものを取得するTimeSpan
  • TimeSpan否定を得るために単項マイナスを使用しますTimeSpan
  • 2を引くとDateTimeTimeSpan
  • 別のものを取得するためにTimeSpana DateTimeを加算または減算しますDateTime

タイプの多くに意味を作ることができ、オペレータの別のセットされています<><=>=。たとえば、BCLではVersionそれらを実装します。


理論的な理論ではなく、非常に現実的な例です!
シズラム

7

私の頭に浮かぶ最初の例は、BigIntegerの実装です。これにより、大きな符号付き整数を扱うことができます。MSDNリンクをチェックして、オーバーロードされたオペレーターの数を確認します(つまり、大きなリストがあり、すべてのオペレーターがオーバーロードされているかどうかは確認しませんでしたが、確かにそうです)

また、私もJavaを実行しており、Javaは演算子のオーバーロードを許可していないため、書くのが非常に甘い

BigInteger bi = new BigInteger(0);
bi += 10;

Javaより:

BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));

5

アイロニーにだまされており、演算子のオーバーロードを大いに使用しているので、これを見たことがうれしいです。サンプルはこちらこれが何ができるかのです。

そのため、Ironyは「.NET言語実装キット」であり、パーサージェネレーター(LALRパーサーを生成)です。yacc / lexなどのパーサージェネレーターのような新しい構文/言語を習得する代わりに、演算子オーバーロードを使用してC#で文法を記述します。これは簡単なBNF文法です

// BNF 
Expr := Term | BinExpr
Term := number | ParExpr
ParExpr := "(" + Expr + ")"
BinExpr := number + BinOp + number
BinOp := "+" | "-" | "*" | "/"
number := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

したがって、これは単純な小さな文法です(BNFを学習して文法を構築しているだけなので、矛盾がある場合はご容赦ください)。C#を見てみましょう。

  var Expr = new NonTerminal("Expr");
  var Term = new NonTerminal("Term");
  var BinExpr = new NonTerminal("BinExpr");
  var ParExpr = new NonTerminal("ParExpr");
  var BinOp = new NonTerminal("BinOp");
  var Statement = new NonTerminal("Statement");
  var ProgramLine = new NonTerminal("ProgramLine");
  var Program = new NonTerminal("Program", typeof(StatementListNode));
  // BNF Rules - Overloading
  Expr.Rule = Term | BinExpr;
  Term.Rule = number | ParExpr;
  ParExpr.Rule = "(" + Expr + ")";
  BinExpr.Rule = Expr + BinOp + Expr;
  BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**";

ご覧のとおり、演算子がオーバーロードしているため、C#で文法を書くことは、BNFで文法を書くこととほぼ同じです。私にとっては、それは理にかなっているだけでなく、演算子のオーバーロードの大きな用途です。


3

重要な例は、operator == / operator!=です。

参照ではなくデータ値で2つのオブジェクトを簡単に比較したい場合は、.Equals(and.GetHashCode!)をオーバーロードし、一貫性のために!=および==演算子を使用することもできます。

しかし、C#で他の演算子のワイルドオーバーロードを見たことはありません(しかし、役に立つかもしれないエッジケースがあると思います)。


1

MSDNのこの例は、複素数を実装し、通常の+演算子を使用する方法を示しています。

別の例では、マトリックスを追加する方法と、ガレージに車を追加するために使用しない方法について説明します(リンクを参照)。


0

オーバーロードを適切に使用することはまれですが、実際には起こります。

operator ==とoperator!=のオーバーロードは、2つの考え方を示します。それを言うのは物事を簡単にし、言うのに反対するのはアドレスの比較を防ぎますオブジェクト)。

特定の状況ではキャスト演算子のオーバーロードが便利だと思います。たとえば、0または1として表されるブール値をXMLでシリアライズ/デシリアライズする必要がありました。ブール演算子からintへの正しい(暗黙的または明示的な)キャスト演算子は、トリックを行いました。


4
アドレスの比較を妨げません:を引き続き使用できますobject.ReferenceEquals()
-dan04

@ dan04知って非常に良い!
MPelletier

アドレスを比較するもう1つの方法は==、キャストによってオブジェクトの使用を強制することです(object)foo == (object)bar。常に参照を比較します。しかしReferenceEquals()、@ dan04が言及しているように、それが何をするのかがより明確だからです。
svick

0

それらは、演算子のオーバーロードのときに人々が一般的に考えるもののカテゴリにはありませんが、オーバーロードできる最も重要な演算子の1つは変換演算子ですです。

変換演算子は、数値型に「脱糖」できる値型、または一部のコンテキストで数値型のように動作できる値型に特に役立ちます。たとえば、特殊な定義かもしれないId特定の識別子を表すタイプを、あなたが提供することができ、暗黙的に変換intを渡すことができるようにId取る方法intが、明示的なのからの変換intには、Id誰もが通過することができないようintId最初にキャストせずにを取得するメソッド。

C#以外の例として、Python言語には、オーバーロード可能な演算子として実装される多くの特別な動作が含まれています。これらには、inメンバーシップテストの()演算子、オブジェクトを関数のように呼び出す演算子、およびlenオブジェクトの長さまたはサイズを決定する演算子が含まれます。

そして、Haskell、Scala、および他の多くの関数型言語のような言語があり、そのような名前+は通常の関数であり、演算子ではありません(そして中置位置で関数を使用するための言語サポートがあります)。


0

System.Drawing名前空間のPoint Structは、オーバーロードを使用して、演算子のオーバーロードを使用して2つの異なる場所を比較します。

 Point locationA = new Point( 50, 50 );
 Point locationB = new Point( 50, 50 );

 if ( locationA == locationB )
    Console.WriteLine( "Their locations are the same" );
 else
    Console.WriteLine( "Their locations  are different" );

ご覧のとおり、オーバーロードを使用して2つの場所のX座標とY座標を比較する方がはるかに簡単です。


0

数学ベクトルに精通している場合は、+演算子のオーバーロードに使用できるかもしれません。を使用してベクターa=[1,3]を追加しb=[2,-1]、get することができますc=[3,2]

イコール(==)をオーバーロードすることも便利です(おそらくequals()メソッドを実装する方がよいでしょうが)。ベクトルの例を続けるには:

v1=[1,3]
v2=[1,3]
v1==v2 // True

-2

フォームに描くためのコードを想像してください

{
  Point p = textBox1.Location;
  Size dp = textBox1.Size;

  // Here the + operator has been overloaded by the CLR
  p += dp;  // Now p points to the lower right corner of the textbox.
  ..
}

もう1つの一般的な例は、構造体を使用して位置情報をベクトル形式で保持する場合です。

public struct Pos
{
    public double x, y, z;
    public double Distance { get { return Math.Sqrt(x * x + y * y + z * z); } }
    public static Pos operator +(Pos A, Pos B)
    {
        return new Pos() { x = A.x + B.x, y = A.y + B.y, z = A.z + B.z };
    }
    public static Pos operator -(Pos A, Pos B)
    {
        return new Pos() { x = A.x - B.x, y = A.y - B.y, z = A.z - B.z };
    }
}

後でのみ使用される

{
    Pos A = new Pos() { x = 4, y = -1, z = 0.5 };
    Pos B = new Pos() { x = 8, y = 2, z = 1.5 };

    double x = (B - A).Distance;
}

4
位置ではなくベクトルを追加します:\これは、オーバーロードしてoperator+はならない場合の良い例です(ベクトルの観点からポイントを実装できますが、2つのポイントを追加することはできません)
BlueRaja-Danny Pflughoeft

@ BlueRaja-DannyPflughoeft:別の位置を得るために位置を追加することは意味がありませんが、それらを平均化するようにそれらを減算することは(ベクトルを生成するために)意味があります。を介してp1、p2、p3、およびp4の平均を計算できますp1+((p2-p1)+(p3-p1)+(p4-p1))/4が、それはやや厄介に思えます。
supercat 14

1
アフィンジオメトリでは、加算、スケーリングなどの点と線を使用して代数を行うことができます。ただし、実装には同種の座標が必要ですが、これは通常3Dグラフィックスで使用されます。2つのポイントを追加すると、実際にはそれらの平均になります。
ja72 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.