インターフェイスベースドプログラミングによる演算子のオーバーロード in C#
質問
背景
私は現在のプロジェクトでインターフェイス ベースのプログラミングを使用していますが、演算子 (特に Equality および Inequality 演算子) をオーバーロードするときに問題に遭遇しました。
前提条件
- C# 3.0、.NET 3.5、および Visual Studio 2008 を使用しています。
UPDATE - 次の仮定は誤りでした!
- operator== ではなく Equals を使用するようすべての比較を要求することは、特にライブラリ (Collections など) に型を渡す場合、実行可能な解決策ではありません。
私が operator== ではなく Equals を使用することを要求することについて懸念していた理由は、.NET ガイドラインのどこにも operator== ではなく Equals を使用することを述べている箇所やそれを示唆している箇所さえ見つけることができなかったからです。しかし、再読したところ Equals および Operator== をオーバーライドするためのガイドライン を読み直したところ、このようになりました。
デフォルトでは、演算子==は2つの参照が同じオブジェクトを示すかどうかを判断することによって、参照の等質性をテストします。したがって、参照型はこの機能を得るために演算子==を実装する必要はありません。型が不変である場合、つまりインスタンスに含まれるデータを変更できない場合、不変オブジェクトとして同じ値を持っている限り同じとみなすことができるため、参照の等質性の代わりに値の等質性を比較するために演算子==をオーバーロードすると便利なことがある。不変でない型に演算子==をオーバーライドするのは得策ではありません。
と、この 等価インターフェイス
IEquatable インターフェースは、Contains、IndexOf、LastIndexOf、Remove などのメソッドで等価性をテストするときに、Dictionary、List、LinkedList などの汎用コレクションオブジェクトで使用されます。これは、一般的なコレクションに格納される可能性のあるあらゆるオブジェクトのために実装されるべきです。
制約事項
- どのような解決策も、オブジェクトをそのインターフェイスからその具象型にキャストすることを必要としません。
問題点
- operator== の両側がインターフェースである場合、基礎となる具象型からの operator== オーバーロード メソッドのシグネチャは一致せず、デフォルトの Object operator== メソッドが呼び出されます。
- クラス上で演算子をオーバーロードする場合、二項演算子のパラメータの少なくとも 1 つは包含型でなければならず、そうでない場合はコンパイラエラーが発生します (エラー BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx )
- インターフェースに実装を指定することはできません。
以下のコードと出力で、この問題のデモをご覧ください。
質問
インターフェースベースのプログラミングを使用する場合、クラスの適切な演算子オーバーロードをどのように提供するのでしょうか?
参考文献
定義済みの値型に対して、等号演算子(==)はオペランドの値が等しい場合に真を、そうでない場合に偽を返します。文字列以外の参照型では、その2つのオペランドが同じオブジェクトを参照していれば真を返します。文字列型の場合、==は文字列の値を比較する。
参照
コード
using System;
namespace OperatorOverloadsWithInterfaces
{
public interface IAddress : IEquatable<IAddress>
{
string StreetName { get; set; }
string City { get; set; }
string State { get; set; }
}
public class Address : IAddress
{
private string _streetName;
private string _city;
private string _state;
public Address(string city, string state, string streetName)
{
City = city;
State = state;
StreetName = streetName;
}
#region IAddress Members
public virtual string StreetName
{
get { return _streetName; }
set { _streetName = value; }
}
public virtual string City
{
get { return _city; }
set { _city = value; }
}
public virtual string State
{
get { return _state; }
set { _state = value; }
}
public static bool operator ==(Address lhs, Address rhs)
{
Console.WriteLine("Address operator== overload called.");
// If both sides of the argument are the same instance or null, they are equal
if (Object.ReferenceEquals(lhs, rhs))
{
return true;
}
return lhs.Equals(rhs);
}
public static bool operator !=(Address lhs, Address rhs)
{
return !(lhs == rhs);
}
public override bool Equals(object obj)
{
// Use 'as' rather than a cast to get a null rather an exception
// if the object isn't convertible
Address address = obj as Address;
return this.Equals(address);
}
public override int GetHashCode()
{
string composite = StreetName + City + State;
return composite.GetHashCode();
}
#endregion
#region IEquatable<IAddress> Members
public virtual bool Equals(IAddress other)
{
// Per MSDN documentation, x.Equals(null) should return false
if ((object)other == null)
{
return false;
}
return ((this.City == other.City)
&& (this.State == other.State)
&& (this.StreetName == other.StreetName));
}
#endregion
}
public class Program
{
static void Main(string[] args)
{
IAddress address1 = new Address("seattle", "washington", "Awesome St");
IAddress address2 = new Address("seattle", "washington", "Awesome St");
functionThatComparesAddresses(address1, address2);
Console.Read();
}
public static void functionThatComparesAddresses(IAddress address1, IAddress address2)
{
if (address1 == address2)
{
Console.WriteLine("Equal with the interfaces.");
}
if ((Address)address1 == address2)
{
Console.WriteLine("Equal with Left-hand side cast.");
}
if (address1 == (Address)address2)
{
Console.WriteLine("Equal with Right-hand side cast.");
}
if ((Address)address1 == (Address)address2)
{
Console.WriteLine("Equal with both sides cast.");
}
}
}
}
出力
Address operator== overload called
Equal with both sides cast.
どのように解決するのですか?
簡単な答えです。2 番目の仮定に欠陥がある可能性があると思います。
Equals()
をチェックするのは正しい方法です。
意味的な等質性
ではなく、2つのオブジェクトの
operator ==
.
長い回答です。演算子のオーバーロードの解決は 実行時ではなく、コンパイル時に実行されます。 .
コンパイラは演算子を適用するオブジェクトの型を決定的に知ることができない限り、コンパイルすることはできません。コンパイラは
IAddress
をオーバーライドするものであることを確認できないので、コンパイラは
==
が定義されている場合、デフォルトの
operator ==
の実装は
System.Object
.
これをより明確に見るために
operator +
に対して
Address
を追加し、2つの
IAddress
のインスタンスを作成します。
に明示的にキャストしない限り
Address
にキャストしない限り、コンパイルに失敗します。なぜか?コンパイラは、ある特定の
IAddress
が
Address
であり、デフォルトの
operator +
の実装にフォールバックすることはできません。
System.Object
.
あなたのフラストレーションの一部は、おそらく
Object
が
operator ==
を実装しており、すべてが
Object
であるため、コンパイラは以下のような操作を正常に解決することができます。
a == b
のような操作を全ての型に対して正常に解決できるようになります。オーバーライドすると
==
をオーバーロードしたとき、同じ動作が見られると期待しましたが、見られませんでした。これは、コンパイラが見つけることができる最も良い一致は、オリジナルの
Object
の実装と最もよく一致するからです。
すべての比較で operator== ではなく Equals を使用するよう要求することは、特にライブラリ (Collections など) に型を渡す場合、実行可能な解決策ではありません。
私の見解では、これはまさにあなたが行うべきことです。
Equals()
をチェックするのは正しい方法です。
意味的な等質性
は、2つのオブジェクトの
時には、意味的な等価性は単なる参照等価性であり、その場合は何も変更する必要はありません。他のケースでは、あなたの例のように、オーバーライドする
Equals
を上書きします。例えば、2 つの
Persons
が同じ社会保障番号を持っていれば等しいと考えたり、 あるいは二つの
Vehicles
は同じ車体番号であれば等しくなります。
しかし
Equals()
と
operator ==
は同じものではありません。オーバーライドする必要があるときはいつでも
operator ==
を上書きする必要があります。
Equals()
を上書きすべきですが、その逆はほとんどありません。
operator ==
はどちらかというと構文的な便宜のためです。CLR 言語 (Visual Basic.NET など) の中には、等号演算子をオーバーライドすることさえ許可していないものがあります。
関連
-
[解決済み】Unity 「関連するスクリプトを読み込むことができません」「Win32Exception: システムは指定されたファイルを見つけることができません"
-
[解決済み】別のスレッドがこのオブジェクトを所有しているため、呼び出し側のスレッドはこのオブジェクトにアクセスできない
-
[解決済み】データが存在しないのに読み込もうとする試みが無効である
-
[解決済み] DateTime型の誕生日から年齢を計算するにはどうしたらいいですか?
-
[解決済み] IDisposable インターフェースの正しい使用法
-
[解決済み] LINQで.Firstと.FirstOrDefaultを使用するタイミングは?
-
[解決済み] ダイレクトキャスト vs 'as'演算子?
-
[解決済み] なぜJavaには演算子のオーバーローディングがないのですか?
-
[解決済み] Javaにおける演算子のオーバーローディング
-
[解決済み] C#の拡張メソッドによる演算子のオーバーロード
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] [Solved] 1つ以上のエンティティで検証に失敗しました。詳細は'EntityValidationErrors'プロパティを参照してください [重複]。
-
[解決済み】「未割り当てのローカル変数を使用」とはどういう意味ですか?
-
解決済み] Critical error detected c0000374 - C++ dll returns pointer off allocated memory to C# [解決済み] Critical error detected c0000374 - C++ dll returns pointer off allocated memory to C#.
-
[解決済み】ソケットのアドレス(プロトコル/ネットワークアドレス/ポート)は、通常1つしか使用できない?
-
[解決済み】ORA-01008: すべての変数がバインドされていません。これらはバインドされています。
-
[解決済み】Unity 「関連するスクリプトを読み込むことができません」「Win32Exception: システムは指定されたファイルを見つけることができません"
-
[解決済み】「...は'型'であり、与えられたコンテキストでは有効ではありません」を解決するにはどうすればよいですか?(C#)
-
[解決済み】ランダムなブーリアンを生成する最速の方法
-
[解決済み] 2つのリストを結合する
-
[解決済み】WebResource.axdとは何ですか?