[英]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.