316

次のように定義されたジェネリックメソッドがあります。

public void MyMethod<T>(T myArgument)

最初にやりたいことは、myArgument の値がその型の既定値であるかどうかを確認することです。たとえば、次のようになります。

if (myArgument == default(T))

しかし、T が == 演算子を実装することを保証していないため、これはコンパイルされません。だから私はこれにコードを切り替えました:

if (myArgument.Equals(default(T)))

これでコンパイルできますが、myArgument が null の場合は失敗します。これは、テスト対象の一部です。次のように明示的な null チェックを追加できます。

if (myArgument == null || myArgument.Equals(default(T)))

今、これは私には冗長に感じます。ReSharper は、myArgument == null 部分を myArgument == default(T) に変更することを提案しています。これが私が始めた場所です。この問題を解決するより良い方法はありますか?

参照型と値型の両方をサポートする必要があります。

4

14 に答える 14

673

箱詰めを避けるために、ジェネリックが等しいかどうかを比較する最良の方法は、 を使用することEqualityComparer<T>.Defaultです。IEquatable<T>これは、 (ボクシングなしで) を尊重しobject.Equals、すべてのNullable<T>「持ち上げられた」ニュアンスを処理します。したがって:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

これは一致します:

  • クラスの null
  • null (空)Nullable<T>
  • 他の構造体の場合は zero/false/etc
于 2009-05-14T18:11:23.613 に答える
121

これはどう:

if (object.Equals(myArgument, default(T)))
{
    //...
}

この方法を使用すると、自分でチェックstatic object.Equals()する必要がなくなります。コンテキストによっては、おそらくnull呼び出しを明示的に修飾する必要はありませんが、コードをよりわかりやすくするために、通常は呼び出しの前に型名を付けます。object.static

于 2008-09-15T18:30:54.180 に答える
31

この問題について詳しく説明しているMicrosoft Connect の記事を見つけることができました。

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

型が参照型であることがわかっている場合、型が独自のカスタム オーバーロードを指定することはできますが、define on object の既定のオーバーロードは参照の等価性について変数をテストします。コンパイラは、変数の静的な型に基づいて、使用するオーバーロードを決定します (決定はポリモーフィックではありません)。したがって、ジェネリック型パラメーター T を封印されていない参照型 (Exception など) に制約するように例を変更すると、コンパイラは使用する特定のオーバーロードを決定でき、次のコードがコンパイルされます。

public class Test<T> where T : Exception

型が値型であることがわかっている場合は、使用されている正確な型に基づいて特定の値の等価性テストを実行します。参照の比較は値の型では意味がなく、コンパイラはどの特定の値の比較を発行するかを知ることができないため、ここでは適切な「デフォルト」の比較はありません。コンパイラは ValueType.Equals(Object) への呼び出しを発行できますが、このメソッドはリフレクションを使用しており、特定の値の比較に比べて非常に非効率的です。したがって、T に値の型の制約を指定したとしても、コンパイラがここで生成することは合理的ではありません。

public class Test<T> where T : struct

あなたが提示した場合、コンパイラは T が値型なのか参照型なのかさえわからない場合、同様に、可能なすべての型に対して有効な生成物は何もありません。参照比較は値型に対して有効ではなく、オーバーロードされていない参照型に対しては、ある種の値比較は予期されません。

これがあなたができることです...

これらのメソッドの両方が、参照型と値型の一般的な比較に機能することを確認しました。

object.Equals(param, default(T))

また

EqualityComparer<T>.Default.Equals(param, default(T))

「==」演算子で比較するには、次のいずれかの方法を使用する必要があります。

T のすべてのケースが既知の基本クラスから派生している場合、ジェネリック型の制限を使用してコンパイラに知らせることができます。

public void MyMethod<T>(T myArgument) where T : MyBase

次に、コンパイラは操作を実行する方法を認識し、現在表示されMyBaseている「演算子 '==' はタイプ 'T' および 'T' のオペランドに適用できません」というエラーをスローしません。

別のオプションは、 T を実装する任意の型に制限することIComparableです。

public void MyMethod<T>(T myArgument) where T : IComparable

そして、 IComparable インターフェイスCompareToで定義されたメソッドを使用します。

于 2008-09-15T18:26:37.110 に答える
21

これを試して:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

それはコンパイルされ、あなたが望むことをするはずです。

于 2008-09-15T18:33:29.090 に答える
7

(編集済み)

Marc Gravell が最良の答えを持っていますが、それを示すために作成した簡単なコード スニペットを投稿したいと思いました。これを単純な C# コンソール アプリで実行するだけです。

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

もう 1 つ: VS2008 を使用している人は、これを拡張メソッドとして試すことができますか? 私はここで 2005 年にこだわっています。それが許されるかどうか知りたいです。


編集:拡張メソッドとして機能させる方法は次のとおりです。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // value type
        Console.WriteLine(1.IsDefault());
        Console.WriteLine(0.IsDefault());

        // reference type
        Console.WriteLine("test".IsDefault());
        // null must be cast to a type
        Console.WriteLine(((String)null).IsDefault());
    }
}

// The type cannot be generic
public static class TypeHelper
{
    // I made the method generic instead
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}
于 2009-05-14T16:14:00.807 に答える
6

T がプリミティブ型である場合を含め、T のすべての型を処理するには、両方の比較方法でコンパイルする必要があります。

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }
于 2009-05-14T16:16:09.427 に答える
3

ここで問題が発生します -

これを任意の型で機能させる場合、default(T) は参照型では常に null になり、値型では 0 (または 0 でいっぱいの構造体) になります。

ただし、これはおそらくあなたが求めている動作ではありません。これを一般的な方法で機能させたい場合は、おそらくリフレクションを使用して T の型をチェックし、参照型とは異なる値型を処理する必要があります。

または、これにインターフェイス制約を設定することもできます。インターフェイスは、クラス/構造体のデフォルトに対してチェックする方法を提供できます。

于 2009-05-14T16:16:55.697 に答える
-1

これが要件で機能するかどうかはわかりませんが、T を IComparable などのインターフェイスを実装する Type に制限し、そのインターフェイス (IIRC が null をサポート/処理する) から ComparesTo() メソッドを使用することができます。 :

public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))

IEquitable など、他にも使用できるインターフェイスがおそらくあるでしょう。

于 2008-09-15T18:32:35.080 に答える
-2

@ilitirit:

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}

演算子'=='は、タイプ'T'および'T'のオペランドには適用できません。

明示的なnullテストに続いて、Equalsメソッドまたはオブジェクトを呼び出すことなくこれを行う方法を考えることはできません。上記のように等しい。

System.Comparisonを使用してソリューションを考案することはできますが、実際には、コードの行数が大幅に増え、複雑さが大幅に増します。

于 2008-09-15T18:37:39.437 に答える
-3

近かったと思います。

if (myArgument.Equals(default(T)))

これでコンパイルできますが、null の場合は失敗しmyArgumentます。これは、テスト対象の一部です。次のように明示的な null チェックを追加できます。

洗練された null セーフ アプローチのために equals が呼び出されているオブジェクトを逆にする必要があるだけです。

default(T).Equals(myArgument);
于 2011-07-28T10:56:46.303 に答える