[英]Checking instance of non-class constrained type parameter for null in generic method
我目前有一個通用方法,在此之前我想對參數進行一些驗證。 具體來說,如果類型參數T
的實例是引用類型,我想檢查它是否為null
,如果它為null
,則拋出ArgumentNullException
。
類似於以下內容:
// This can be a method on a generic class, it does not matter.
public void DoSomething<T>(T instance)
{
if (instance == null) throw new ArgumentNullException("instance");
注意,我不想使用class
約束來約束我的類型參數。
我以為可以使用Marc Gravell關於“如何將泛型類型與其默認值進行比較?” 的答案 。 ,並使用EqualityComparer<T>
類,如下所示:
static void DoSomething<T>(T instance)
{
if (EqualityComparer<T>.Default.Equals(instance, null))
throw new ArgumentNullException("instance");
但這在調用Equals
時給出了非常模糊的錯誤:
成員'object.Equals(object,object)'不能通過實例引用進行訪問; 用類型名稱代替它
當T
不受值或引用類型的約束時,如何檢查T
的實例是否為null
?
有幾種方法可以做到這一點。 通常,在框架中(如果您通過Reflector查看源代碼),您會看到將type參數實例轉換為object
,然后針對null
進行檢查,如下所示:
if (((object) instance) == null)
throw new ArgumentNullException("instance");
在大多數情況下,這很好。 但是,有一個問題。
考慮以下五種主要情況,其中可以針對null檢查T
的無約束實例:
Nullable<T>
的值類型的實例 Nullable<T>
但不是null
Nullable<T>
但是是null
null
null
的實例 在大多數情況下,性能都不錯,但是在與Nullable<T>
進行比較的情況下,性能受到嚴重影響,一種情況下超過一個數量級,而另一種情況下至少是其五倍。案件。
首先,讓我們定義方法:
static bool IsNullCast<T>(T instance)
{
return ((object) instance == null);
}
以及測試工具方法:
private const int Iterations = 100000000;
static void Test(Action a)
{
// Start the stopwatch.
Stopwatch s = Stopwatch.StartNew();
// Loop
for (int i = 0; i < Iterations; ++i)
{
// Perform the action.
a();
}
// Write the time.
Console.WriteLine("Time: {0} ms", s.ElapsedMilliseconds);
// Collect garbage to not interfere with other tests.
GC.Collect();
}
應該指出這一點,因為它需要進行一千萬次迭代。
毫無疑問,這是沒有關系的,通常,我會同意。 然而,我發現這個在遍歷一個非常大的數據集在緊湊循環(建築決策樹與數以百計的每個屬性的數萬項)的過程中,這是一個明確的因素。
也就是說,以下是針對投放方法的測試:
Console.WriteLine("Value type");
Test(() => IsNullCast(1));
Console.WriteLine();
Console.WriteLine("Non-null nullable value type");
Test(() => IsNullCast((int?)1));
Console.WriteLine();
Console.WriteLine("Null nullable value type");
Test(() => IsNullCast((int?)null));
Console.WriteLine();
// The object.
var o = new object();
Console.WriteLine("Not null reference type.");
Test(() => IsNullCast(o));
Console.WriteLine();
// Set to null.
o = null;
Console.WriteLine("Not null reference type.");
Test(() => IsNullCast<object>(null));
Console.WriteLine();
輸出:
Value type
Time: 1171 ms
Non-null nullable value type
Time: 18779 ms
Null nullable value type
Time: 9757 ms
Not null reference type.
Time: 812 ms
Null reference type.
Time: 849 ms
注意,在非null Nullable<T>
以及null Nullable<T>
; 第一種方法比檢查非Nullable<T>
的值類型要慢十五倍,而第二種方法至少要慢八倍。
這樣做的原因是拳擊。 對於傳入的每個Nullable<T>
實例,在強制轉換為object
進行比較時,必須將值類型裝箱,這意味着在堆上進行分配等。
但是,可以通過動態編譯代碼來改進這一點。 可以定義一個幫助程序類,該類將提供對IsNull
的調用的實現,該調用是在創建類型時即時分配的,如下所示:
static class IsNullHelper<T>
{
private static Predicate<T> CreatePredicate()
{
// If the default is not null, then
// set to false.
if (((object) default(T)) != null) return t => false;
// Create the expression that checks and return.
ParameterExpression p = Expression.Parameter(typeof (T), "t");
// Compare to null.
BinaryExpression equals = Expression.Equal(p,
Expression.Constant(null, typeof(T)));
// Create the lambda and return.
return Expression.Lambda<Predicate<T>>(equals, p).Compile();
}
internal static readonly Predicate<T> IsNull = CreatePredicate();
}
注意事項:
default(T)
結果的實例強制轉換為object
,以查看類型是否可以分配null
。 可以在這里做,因為每個被調用的類型只被調用一次 。 T
的默認值不為null
,則假定無法將null
分配給T
的實例。 在這種情況下,沒有必要使用Expression
類實際生成lambda,因為條件始終為false。 null
,那么創建一個與空值比較的lambda表達式就很容易了,然后即時對其進行編譯。 現在,運行此測試:
Console.WriteLine("Value type");
Test(() => IsNullHelper<int>.IsNull(1));
Console.WriteLine();
Console.WriteLine("Non-null nullable value type");
Test(() => IsNullHelper<int?>.IsNull(1));
Console.WriteLine();
Console.WriteLine("Null nullable value type");
Test(() => IsNullHelper<int?>.IsNull(null));
Console.WriteLine();
// The object.
var o = new object();
Console.WriteLine("Not null reference type.");
Test(() => IsNullHelper<object>.IsNull(o));
Console.WriteLine();
Console.WriteLine("Null reference type.");
Test(() => IsNullHelper<object>.IsNull(null));
Console.WriteLine();
輸出為:
Value type
Time: 959 ms
Non-null nullable value type
Time: 1365 ms
Null nullable value type
Time: 788 ms
Not null reference type.
Time: 604 ms
Null reference type.
Time: 646 ms
在上述兩種情況下,這些數字要好得多,而在其他兩種情況下,總體要好(盡管可以忽略不計)。 沒有裝箱,並且Nullable<T>
被復制到堆棧上,這比在堆上創建新對象(先前的測試正在做) 要快得多。
可以走得更遠,並使用Reflection Emit即時生成一個接口實現,但是我發現結果可以忽略不計,甚至比使用編譯的lambda還差。 該代碼也更難以維護,因為您必須為該類型以及可能的程序集和模塊創建新的生成器。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.