目前,我有,我想对他们的工作之前做的一些参数验证的通用方法。 具体而言,如果类型参数的实例T
是引用类型,我要检查,看它是否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
约束 。
我以为我可以用马克Gravell的答案在“我怎么比一般类型为默认值?” ,并使用EqualityComparer<T>
类 ,如下所示:
static void DoSomething<T>(T instance)
{
if (EqualityComparer<T>.Default.Equals(instance, null))
throw new ArgumentNullException("instance");
但它给在电话会议中一个非常明确的错误Equals
:
构件“的Object.Equals(对象,对象)”不能以一个实例引用来访问; 与类型名来限定它,而不是
如何检查的实例T
对null
时T
不上是一个值或引用类型的限制?
有几个方法可以做到这一点。 通常情况下,在框架(如果你看看通过反射源代码),你会看到类型参数的实例强制转换为object
,然后检查,对null
,如下所示:
if (((object) instance) == null)
throw new ArgumentNullException("instance");
而且在大多数情况下,这是好的。 然而,有一个问题。
考虑这样的无约束情况下的五个主要情况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
注意在一个非空的情况下Nullable<T>
以及一个空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
。 在这种情况下,没有理由实际生成使用一个lambda Expression
类 ,作为条件始终为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>
被拷贝到堆栈,这是比在堆(这在现有试验做)创建一个新对象快得多的操作。
人们可以更进一步,使用反射发出来实时生成的接口实现,但我发现效果可以忽略不计,如果不是使用编译拉姆达更糟。 代码也更难维护,因为你必须创建类型的新建设者,以及可能的组件和模块。
文章来源: Checking instance of non-class constrained type parameter for null in generic method