私はこのように定義された一般的なメソッドを持っています:
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)に変更することを提案しています。この問題を解決するより良い方法はありますか?
サポートする必要があります 両方 型と値型を参照します。
ボクシングを避けるために、ジェネリックの同等性を比較する最良の方法はEqualityComparer<T>.Default
を使用することです。これは、IEquatable<T>
(ボクシングなし)とobject.Equals
を尊重し、すべてのNullable<T>
「持ち上げられた」ニュアンスを処理します。したがって:
if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
return obj;
}
これは一致します:
Nullable<T>
に対してnull(空)これはどう:
if (object.Equals(myArgument, default(T)))
{
//...
}
static object.Equals()
メソッドを使用すると、null
チェックを行う必要がなくなります。コンテキストによっては、object.
で明示的に呼び出しを修飾する必要はないかもしれませんが、通常、コードをよりわかりやすくするために、static
呼び出しの前に型名を付けます。
私は 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
メソッドを使用します。
これを試して:
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));
}
}
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;
}
ここで問題が発生します-
これを任意の型で機能させる場合、default(T)は参照型では常にnull、値型では0(または0でいっぱいの構造体)になります。
ただし、これはおそらくあなたが望んでいる動作ではありません。これを一般的な方法で機能させたい場合は、おそらくリフレクションを使用してTの型を確認し、参照型とは異なる値型を処理する必要があります。
あるいは、これにインターフェース制約を設定し、インターフェースがクラス/構造のデフォルトをチェックする方法を提供することもできます。
このロジックを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) ...
}
私が使う:
public class MyClass<T>
{
private bool IsNull()
{
var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
}
}