C#のジェネリック引数のnullまたはデフォルトの比較


288

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

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)に変更することを提案しています。この問題を解決するより良い方法はありますか?

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


C#はNull条件演算子をサポートするようになりました。これは、最後に指定した例の構文糖です。コードはになりif (myArgument?.Equals( default(T) ) != null )ます。
wizard07KSU 2017年

1
@ wizard07KSUこれは値の型では機能しません。つまりtrueEqualswilは常に値の型に対して呼び出されるため、この場合myArgumentは不可能でありnullEquals(ブール値)の結果は決してならないためnullです。
ジャスパー

同等に価値のあるほぼ重複する(したがって、閉じるには投票しない):演算子==をC#のジェネリック型に適用できませんか?
GSerg

回答:


582

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

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

これは一致します:

  • クラスの場合はnull
  • ヌル(空) Nullable<T>
  • 他の構造体の場合はゼロ/偽/など

28
わあ、なんて楽しいのかわかりません!これは確かに進むべき道だと思います。
Nick Farina、

1
間違いなく最良の答えです。このソリューションを使用するように書き換えた後、コードに波線はありません。
Nathan Ridley、

13
正解です。さらによいのは、このコード行に拡張メソッドを追加して、obj.IsDefaultForType()を実行できるようにすることです
rikoe

2
以下の場合@nawfalはPersonp1.Equals(p2)それが実装しているかどうかに依存するIEquatable<Person>公開APIに、または明示的な実装を介した-つまり、コンパイラは、パブリック見ることができるEquals(Person other)方法を。しかしながら; でジェネリック医薬品、同じILはすべてのために使用されていますTT1たまたま実装IEquatable<T1>するa は、実装T2しないa と同じように処理する必要があります。つまり、実行時に存在する場合でも、メソッドを特定しませんEquals(T1 other)。どちらの場合も、null(どちらかのオブジェクト)についても考える必要があります。そのため、ジェネリックスでは、投稿したコードを使用します。
Marc Gravell

5
この答えが私を狂気から遠ざけたか、または狂気に近づけたかを決定することはできません。+1
スティーブン・リーケンス2017

118

これはどう:

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

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


2
「オブジェクト」をドロップすることもできます。冗長なので一部。if(Equals(myArgument、default(T)))
Stefan Moser

13
確かに、それは通常そうですが、コンテキストに依存しない場合があります。2つの引数を取るインスタンスEquals()メソッドが存在する場合があります。コードを読みやすくするためだけに、静的呼び出しの前にクラス名を明示的に付ける傾向があります。
ケントブーガート2008

8
それはボクシングを引き起こすことに注意する必要があり、いくつかのケースではそれが重要かもしれません
ナイトコーダー

2
私にとっては、すでにボックス化されている整数を使用している場合、これは機能しません。それは、オブジェクトとオブジェクトのデフォルトとなりますので、ヌルの代わりに、0です
riezeboschは

28

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

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

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

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

次にCompareToIComparableインターフェイスで定義されたメソッドを使用します


4
「この動作は仕様によるものであり、値型を含む可能性のある型パラメーターでの使用を可能にする簡単な解決策はありません。」実際、マイクロソフトは間違っています。簡単な解決策があります。MSはceqオペコードを拡張して、ビット型演算子として値型で機能するようにする必要があります。次に、このオペコードを単に使用する組み込み関数を提供できます。たとえば、単にceqを使用するobject.BitwiseOrReferenceEquals <T>(value、default(T))です。値タイプと参照タイプの両方で、これは値のビット単位の等価性チェックします(ただし、参照タイプの場合、参照ビット単位の等価性はobject.ReferenceEqualsと同じです)
Qwertie

1
私はあなたが望んでいたマイクロソフト接続リンクがだったと思うconnect.microsoft.com/VisualStudio/feedback/details/304501/...
Qwertie

18

これを試して:

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

コンパイルして、必要な処理を行う必要があります。


<code> default(T)</ code>は冗長ではありませんか?<code> EqualityComparer <T> .Default.Equals(myArgument)</ code>がうまくいくはずです。
Joshcodes 2013年

2
1)試したか、2)次に何を比較するのか、comparerオブジェクト?のEqualsメソッドIEqualityComparerは2つの引数、つまり比較する2つのオブジェクトを取るため、いいえ、冗長ではありません。
Lasse V. Karlsen

これは、ボクシング/アンボクシングやその他のタイプを処理するため、受け入れられた回答のIMHOよりも優れています。この「クローズ
ドア

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));
    }
}

3
拡張メソッドとして「機能」します。これは、oがnullのときにo.IsDefault <object>()と言っても機能するので興味深いです。怖い=)
ニックファリーナ

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;
    }

1
Func <T>を受け入れてTを返すように関数が変更されていることに注意してください。これは、質問者のコードから誤って省略されたと思います。
Nick Farina、

ReSharperは私を混乱させているようです。値の型とnullの比較の可能性に関する警告がコンパイラの警告ではないことに気づかなかった。
Nathan Ridley、

2
参考:Tが値型であることが判明した場合、nullとの比較はジッターによって常にfalseとして扱われます。
Eric Lippert、

理にかなっている-ランタイムはポインターを値型と比較します。ただし、その場合はEquals()チェックは機能します(興味深いことに、コンパイルを行う5.Equals(4)と言うのは非常に動的な言語のようです)。
Nick Farina、

2
ボクシングなどを含まない別の方法については、EqualityComparer <T>の回答を参照してください
Marc Gravell

2

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

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

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

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


1

このロジックを2つの部分に分割し、最初にnullを確認する必要があると思います。

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

IsNullメソッドでは、定義ではValueTypeオブジェクトをnullにすることはできないため、値がValueTypeから派生したクラスである場合、nullではないことがわかっています。一方、値型でない場合は、オブジェクトにキャストされた値をnullと比較するだけです。オブジェクトへのキャストに直接進むことで、ValueTypeに対するチェックを回避できますが、それは値の型がボックス化されることを意味します。これは、ヒープ上に新しいオブジェクトが作成されることを意味するため、おそらく回避したいものです。

IsNullOrEmptyメソッドでは、文字列の特殊なケースをチェックしています。他のすべてのタイプでは、値(すでにnull でないことがわかってます)をデフォルト値と比較します。デフォルト値はすべての参照タイプではnullであり、値タイプでは通常、何らかの形式のゼロ(それらが整数の場合)です。

これらのメソッドを使用すると、次のコードは期待どおりに動作します。

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}

1

受け入れられた回答に基づく拡張方法。

   public static bool IsDefault<T>(this T inObj)
   {
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

使用法:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue == null || tValue.IsDefault()) return false;
   }

単純化するためにnullで代替:

   public static bool IsNullOrDefault<T>(this T inObj)
   {
       if (inObj == null) return true;
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

使用法:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue.IsNullOrDefault()) return false;
   }

0

私が使う:

public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}

-1

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

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

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


OPはNullReferenceExceptionを心配しており、あなたは彼に同じことを保証しています。
nawfal 2013

-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メソッドまたはobject.Equalsの呼び出しなしに、これを行う方法は考えられません。

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


-3

あなたは近くにいたと思います。

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

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

エレガントなnullセーフアプローチでは、equalsが呼び出されているオブジェクトを逆にするだけです。

default(T).Equals(myArgument);

私はまったく同じことを考えていました。
Chris Gessler、2012年

6
参照型のdefault(T)がnullであり、保証されたNullReferenceExceptionが発生します。
Stefan Steinegger 2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.