web-dev-qa-db-ja.com

演算子==はC#のジェネリック型に適用できませんか?

MSDN==演算子のドキュメントによると、

定義済みの値型の場合、等値演算子(==)は、オペランドの値が等しい場合はtrueを返し、そうでない場合はfalseを返します。文字列以外の参照型の場合、==は、2つのオペランドが同じオブジェクトを参照する場合にtrueを返します。文字列タイプの場合、==は文字列の値を比較します。ユーザー定義の値型は==演算子をオーバーロードできます(演算子を参照)。 デフォルトでは==は定義済みおよびユーザー定義の両方の参照タイプについて上記のように動作しますが、ユーザー定義の参照タイプも使用できます。

では、なぜこのコードスニペットはコンパイルに失敗するのですか?

bool Compare<T>(T x, T y) { return x == y; }

エラーが発生しました演算子 '=='は、タイプ 'T'および 'T'のオペランドには適用できません。私が理解している限り、==演算子はすべての型に対して事前定義されているので、なぜだろうか。

編集:みなさん、ありがとう。最初は、ステートメントが参照型のみに関するものであることに気付きませんでした。また、すべての値の種類についてビットごとの比較が提供されると考えましたが、これはnotが正しいことがわかっています。

しかし、参照型を使用している場合、==演算子は定義済みの参照比較を使用しますか、または型が定義されている場合は演算子のオーバーロードバージョンを使用しますか?

Edit 2:試行錯誤を通して、無制限のジェネリック型を使用する場合、==演算子は定義済みの参照比較を使用することがわかりました。実際、コンパイラは制限された型引数に対して見つけることができる最良の方法を使用しますが、それ以上は調べません。たとえば、以下のコードは、Test.test<B>(new B(), new B())が呼び出された場合でも、常にtrueを出力します。

class A { public static bool operator==(A x, A y) { return true; } }
class B : A { public static bool operator==(B x, B y) { return false; } }
class Test { void test<T>(T a, T b) where T : A { Console.WriteLine(a == b); } }
304
Hosam Aly

「...既定では==は、定義済みおよびユーザー定義の両方の参照タイプについて上記のように動作します。」

タイプTは必ずしも参照タイプであるとは限らないため、コンパイラーはそのような仮定をすることはできません。

ただし、より明示的であるため、これはコンパイルされます。

    bool Compare<T>(T x, T y) where T : class
    {
        return x == y;
    }

追加の質問に続いて、「ただし、参照型を使用している場合、==演算子は定義済みの参照比較を使用するか、型が定義されている場合は演算子のオーバーロードバージョンを使用しますか? "

Genericsの==はオーバーロードされたバージョンを使用すると考えていましたが、次のテストはそれ以外を示しています。興味深い...理由を知りたい!誰かが知っている場合は共有してください。

namespace TestProject
{
 class Program
 {
    static void Main(string[] args)
    {
        Test a = new Test();
        Test b = new Test();

        Console.WriteLine("Inline:");
        bool x = a == b;
        Console.WriteLine("Generic:");
        Compare<Test>(a, b);

    }


    static bool Compare<T>(T x, T y) where T : class
    {
        return x == y;
    }
 }

 class Test
 {
    public static bool operator ==(Test a, Test b)
    {
        Console.WriteLine("Overloaded == called");
        return a.Equals(b);
    }

    public static bool operator !=(Test a, Test b)
    {
        Console.WriteLine("Overloaded != called");
        return a.Equals(b);
    }
  }
}

出力

インライン:オーバーロード==呼び出し

ジェネリック:

何かキーを押すと続行します 。 。 。

フォローアップ2

私は比較方法を変更することを指摘したいです

    static bool Compare<T>(T x, T y) where T : Test
    {
        return x == y;
    }

オーバーロードされた==演算子が呼び出されます。型を指定せずに(whereとして)、コンパイラはオーバーロードされた演算子を使用する必要があると推測することはできません...タイプを指定しなくても、その決定を下すのに十分な情報があると思います。

131
Giovanni Galbo

他の人が言ったように、Tが参照型に制限されている場合にのみ機能します。制約なしで、nullと比較できますが、nullのみです。nullできない値の型では、その比較は常にfalseです。

Equalsを呼び出す代わりに、IComparer<T>を使用することをお勧めします。さらに情報がない場合は、EqualityComparer<T>.Defaultを選択することをお勧めします。

public bool Compare<T>(T x, T y)
{
    return EqualityComparer<T>.Default.Equals(x, y);
}

他のこととは別に、これはボクシング/キャスティングを回避します。

279
Jon Skeet

一般に、EqualityComparer<T>.Default.EqualsIEquatable<T>を実装するもの、または賢明なEquals実装を持つものでジョブを実行する必要があります。

ただし、何らかの理由で==Equalsが異なる方法で実装されている場合は、 汎用演算子 の作業が役立つはずです。 (特に)のoperatorバージョンをサポートしています。

  • 等しい(T value1、T value2)
  • NotEqual(T value1、T value2)
  • GreaterThan(T value1、T value2)
  • LessThan(T value1、T value2)
  • GreaterThanOrEqual(T value1、T value2)
  • LessThanOrEqual(T value1、T value2)
40
Marc Gravell

非常に多くの回答があり、単一の回答では説明できないのはなぜですか? (ジョバンニが明示的に尋ねた)...

.NETジェネリックは、C++テンプレートのようには機能しません。 C++テンプレートでは、実際のテンプレートパラメータがわかった後、オーバーロードの解決が行われます。

.NETジェネリック(C#を含む)では、実際のジェネリックパラメーターを知らなくてもオーバーロードの解決が行われます。コンパイラが呼び出す関数を選択するために使用できる唯一の情報は、ジェネリックパラメータの型制約から取得されます。

26
Ben Voigt

コンパイルでは、Tが構造体(値型)になれないことがわかりません。だからあなたはそれが私が思う参照型のものにしかなれないことを伝えなければなりません:

bool Compare<T>(T x, T y) where T : class { return x == y; }

これは、Tが値型になる可能性がある場合、型に演算子==が定義されていない場合にx == yが正しく形成されない場合があるためです。これについても同じことが起こります。これはより明白です。

void CallFoo<T>(T x) { x.foo(); }

関数fooを持たない型Tを渡すことができるため、これも失敗します。 C#では、可能なすべての型が常に関数fooを持つように強制します。それはwhere句によって行われます。

クラス制約のないようです:

bool Compare<T> (T x, T y) where T: class
{
    return x == y;
}

==演算子のclass制約付きEqualsObject.Equalsを継承し、構造体のそれはValueType.Equalsをオーバーライドします。

ご了承ください:

bool Compare<T> (T x, T y) where T: struct
{
    return x == y;
}

また、同じコンパイラエラーが発生します。

まだ、値型の等価演算子の比較がコンパイラによって拒否される理由を理解していません。しかし、事実、これが機能することは知っています。

bool Compare<T> (T x, T y)
{
    return x.Equals(y);
}
8
Jon Limjap

カスタム型の演算子が呼び出されることを確認したい場合は、リフレクションを介して行うことができます。ジェネリックパラメーターを使用して型を取得し、目的の演算子(たとえば、op_Equality、op_Inequality、op_LessThan ...)のMethodInfoを取得します。

var methodInfo = typeof (T).GetMethod("op_Equality", 
                             BindingFlags.Static | BindingFlags.Public);    

次に、MethodInfoのInvokeメソッドを使用して演算子を実行し、オブジェクトをパラメーターとして渡します。

var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2});

これにより、ジェネリックパラメーターに適用された制約によって定義された演算子ではなく、オーバーロードされた演算子が呼び出されます。実用的ではないかもしれませんが、2、3のテストを含む一般的な基本クラスを使用する場合、オペレーターのユニットテストに役立ちます。

4
Christophe

このためのMSDN Connectエントリがあります here

アレックスターナーの返事は:

残念ながら、この動作は仕様によるものであり、値型を含む可能性のある型パラメーターで==を使用できるようにする簡単な解決策はありません。

4
Recep

私の場合、等価演算子の単体テストを行いたいと思いました。ジェネリック型を明示的に設定せずに、等価演算子の下でコードを呼び出す必要がありました。 EqualityComparerのアドバイスは、EqualityComparerEqualsメソッドを呼び出しましたが、等価演算子ではないため、役に立ちませんでした。

LINQを構築することで、ジェネリック型でこれを機能させる方法を次に示します。 ==および!=演算子の正しいコードを呼び出します。

/// <summary>
/// Gets the result of "a == b"
/// </summary>
public bool GetEqualityOperatorResult<T>(T a, T b)
{
    // declare the parameters
    var paramA = Expression.Parameter(typeof(T), nameof(a));
    var paramB = Expression.Parameter(typeof(T), nameof(b));
    // get equality expression for the parameters
    var body = Expression.Equal(paramA, paramB);
    // compile it
    var invokeEqualityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
    // call it
    return invokeEqualityOperator(a, b);
}

/// <summary>
/// Gets the result of "a =! b"
/// </summary>
public bool GetInequalityOperatorResult<T>(T a, T b)
{
    // declare the parameters
    var paramA = Expression.Parameter(typeof(T), nameof(a));
    var paramB = Expression.Parameter(typeof(T), nameof(b));
    // get equality expression for the parameters
    var body = Expression.NotEqual(paramA, paramB);
    // compile it
    var invokeInequalityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
    // call it
    return invokeInequalityOperator(a, b);
}
4
U. Bulle

最新のmsdnを見て、次の関数を作成しました。 2つのオブジェクトxyを簡単に比較できます。

static bool IsLessThan(T x, T y) 
{
    return ((IComparable)(x)).CompareTo(y) <= 0;
}
2
Charlie

bool Compare(T x, T y) where T : class { return x == y; }

ユーザー定義の参照型の場合は==が処理されるため、上記が機能します。
値タイプの場合、==をオーバーライドできます。その場合、「!=」も定義する必要があります。

それが理由かもしれないと思う、それは「==」を使用した一般的な比較を許可しない。

1
shahkalpesh