web-dev-qa-db-ja.com

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

私はこのように定義された一般的なメソッドを持っています:

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

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

259
Stefan Moser

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

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

これは一致します:

  • クラスの場合はnull
  • Nullable<T>に対してnull(空)
  • 他の構造体の場合はゼロ/ false /など
519
Marc Gravell

これはどう:

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

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

114
Kent Boogaart

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

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

型が参照型であることがわかっている場合、オブジェクトで定義されたデフォルトのオーバーロードは、参照の等価性について変数をテストしますが、型は独自のカスタムオーバーロードを指定できます。コンパイラーは、変数の静的型に基づいて使用するオーバーロードを決定します(決定は多態性ではありません)。したがって、ジェネリック型パラメーター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

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

26
Eric Schoonover

これを試して:

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

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

(編集済み)

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

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;
    }
6
Nick Farina

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

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

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

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

2
Reed Copsey

このロジックを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にできないという事実に依存しているため、valueがValueTypeから派生するクラスである場合、nullでないことが既にわかっています。一方、値型でない場合は、オブジェクトにキャストされた値をnullと比較するだけです。オブジェクトへのキャストに直接進むことでValueTypeに対するチェックを回避できますが、それは値型がボックス化されることを意味します。

IsNullOrEmptyメソッドでは、文字列の特殊なケースをチェックしています。他のすべての型については、値(not 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
Damian Powell

私が使う:

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