检查非类约束类型参数的实例在泛型方法无效 [英] Checking instance of non-class constrained type parameter for null in generic method
问题描述
目前,我有我想要对他们的工作之前,做的一些参数验证的通用方法。具体而言,如果类型参数 T
的实例是一个引用类型,我要检查,看它是否空
并抛出一个 ArgumentNullException
如果它是空。
I currently have a generic method where I want to do some validation on the parameters before working on them. Specifically, if the instance of the type parameter T
is a reference type, I want to check to see if it's null
and throw an ArgumentNullException
if it's null.
线沿线的东西:
// 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");
请注意,我不希望使用 类
约束。
Note, I do not wish to constrain my type parameter using the class
constraint.
我想我可以使用马克Gravell的回答上的我怎么泛型类型比较其默认值?和使用的 EqualityComparer&LT; T&GT;
类像这样:
I thought I could use Marc Gravell's answer on "How do I compare a generic type to its default value?", and use the EqualityComparer<T>
class like so:
static void DoSomething<T>(T instance)
{
if (EqualityComparer<T>.Default.Equals(instance, null))
throw new ArgumentNullException("instance");
但它给出了一个电话很暧昧的错误 等于
:
的成员的Object.Equals(对象,对象)'不能与实例引用访问;与类型名称限定它,而不是
Member 'object.Equals(object, object)' cannot be accessed with an instance reference; qualify it with a type name instead
我如何检查 T
的实例,对空
在 T
不受限于作为一个值或引用类型?
How can I check an instance of T
against null
when T
is not constrained on being a value or reference type?
推荐答案
有几个方法可以做到这一点。通常情况下,在该框架(如果你通过反射源$ C $ C),你会看到一个演员的类型参数的实例的对象
,然后检查这对空
,像这样:
There's a few ways to do this. Often, in the framework (if you look at source code through Reflector), you'll see a cast of the instance of the type parameter to object
and then checking that against null
, like so:
if (((object) instance) == null)
throw new ArgumentNullException("instance");
和在大多数情况下,这是好的。但是,有一个问题。
And for the most part, this is fine. However, there's a problem.
考虑五个主要的情况下,不受约束的情况下 T
可以对空检查:
Consider the five main cases where an unconstrained instance of T
could be checked against null:
- 的的的值类型的实例是不是的
可空&LT; T&GT;
- 的的的值类型的实例是的
可空&LT; T&GT;
而不是空
- 的的的值类型的实例是的
可空&LT; T&GT;
,但空
- 引用类型不是的实例
空
- 引用类型是
空
的实例
- An instance of a value type that is not
Nullable<T>
- An instance of a value type that is
Nullable<T>
but is notnull
- An instance of a value type that is
Nullable<T>
but isnull
- An instance of a reference type that is not
null
- An instance of a reference type that is
null
在大多数情况下,性能优良,但在你的比对可空&LT的情况下,T&GT;
,有一个严重的性能,一个多量级在一种情况下和至少五倍在另一种情况下
In most of these cases, the performance is fine, but in the cases where you are comparing against Nullable<T>
, there's a severe performance hit, more than an order of magnitude in one case and at least five times as much in the other case.
首先,让我们定义的方法:
First, let's define the method:
static bool IsNullCast<T>(T instance)
{
return ((object) instance == null);
}
还有测试工具方法:
As well as the test harness method:
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();
}
东西应该说对,它需要十百万次迭代指出这一点的事实。
Something should be said about the fact that it takes ten million iterations to point this out.
肯定有一种观点认为无所谓,而通常情况下,我会同意。然而,我发现这个在迭代的过程中通过的非常的大集(与数以百计的每个属性构建决策树的项目数以万计)在紧密循环的数据,这是一个明确的因素
There's definitely an argument that it doesn't matter, and normally, I'd agree. However, I found this over the course of iterating over a very large set of data in a tight loop (building decision trees for tens of thousands of items with hundreds of attributes each) and it was a definite factor.
这是说,这里是针对铸造方法测试:
That said, here are the tests against the casting method:
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
请注意在的情况下,非空可空&LT; T&GT;
以及空可空&LT; T&GT;
;首先是比核对数值类型不是可空&LT慢了十五倍; T&GT;
,而第二种是至少八倍慢
Note in the case of a non-null Nullable<T>
as well as a null Nullable<T>
; the first is over fifteen times slower than checking against a value type that is not Nullable<T>
while the second is at least eight times as slow.
这样做的原因是拳击。对于可空与其中的每一个实例; T&GT;
传递中,铸造对象
的比较时,值类型已被装箱,这意味着在堆上等的分配。
The reason for this is boxing. For every instance of Nullable<T>
that is passed in, when casting to object
for a comparison, the value type has to be boxed, which means an allocation on the heap, etc.
这可以在但是,提高,通过编译code的飞行。一个辅助类可以被定义,这将提供一个呼叫的实施 ISNULL
,分配在飞行中被创建的类型时,像这样:
This can be improved upon, however, by compiling code on the fly. A helper class can be defined which will provide the implementation of a call to IsNull
, assigned on the fly when the type is created, like so:
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();
}
有几件事情需要注意:
A few things to note:
- 我们实际上正在使用铸造结果的实例同样的伎俩
默认(T)
以,以便对象
来看看,如果该类型的可以已空
分配给它。这是确定在这里做的,因为它只能被称为的每一次键入的,这是被称为了。 - 如果对
T
默认值不是空
,那么它假定空
不能分配给T
的实例。在这种情况下,没有理由实际生成使用的防爆pression
类,视病情始终为false。 - 如果类型的可以的有
空
分配给它,那么它很容易地创建一个lambda EX pression其中比较对空然后在即时编译。
- We're actually using the same trick of casting the instance of the result of
default(T)
toobject
in order to see if the type can havenull
assigned to it. It's ok to do here, because it's only being called once per type that this is being called for. - If the default value for
T
is notnull
, then it's assumednull
cannot be assigned to an instance ofT
. In this case, there's no reason to actually generate a lambda using theExpression
class, as the condition is always false. - If the type can have
null
assigned to it, then it's easy enough to create a lambda expression which compares against null and then compile it on-the-fly.
现在,运行此测试:
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
这些数字的多的更好,在上述两种情况,而在其他总体较好(虽然可以忽略不计)。有没有拳击,和可空&LT; T&GT;
复制到堆栈,这是一个的多的更快的操作比在堆上创建新对象(该测试之前在做)。
These numbers are much better in the two cases above, and overall better (although negligible) in the others. There's no boxing, and the Nullable<T>
is copied onto the stack, which is a much faster operation than creating a new object on the heap (which the prior test was doing).
一的可以的更进一步,使用反射发出来实时生成一个接口实现,但我发现结果是可以忽略不计,如果不差于使用编译的lambda。在code也更加难以维持,因为你有可能创造新的制造商的类型,以及一个装配和模块。
One could go further and use Reflection Emit to generate an interface implementation on the fly, but I've found the results to be negligible, if not worse than using a compiled lambda. The code is also more difficult to maintain, as you have to create new builders for the type, as well as possibly an assembly and module.
这篇关于检查非类约束类型参数的实例在泛型方法无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!