检查非类约束类型参数的实例在泛型方法无效 [英] Checking instance of non-class constrained type parameter for null in generic method

查看:167
本文介绍了检查非类约束类型参数的实例在泛型方法无效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前,我有我想要对他们的工作之前,做的一些参数验证的通用方法。具体而言,如果类型参数 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 not null
  • An instance of a value type that is Nullable<T> but is null
  • 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) to object in order to see if the type can have null 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 not null, then it's assumed null cannot be assigned to an instance of T. In this case, there's no reason to actually generate a lambda using the Expression 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆