检查非类约束类型参数的实例为在通用的方法空(Checking instance of non-cla

2019-09-22 08:26发布

目前,我有,我想对他们的工作之前做的一些参数验证的通用方法。 具体而言,如果类型参数的实例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(对象,对象)”不能以一个实例引用来访问; 与类型名来限定它,而不是

如何检查的实例TnullT不上是一个值或引用类型的限制?

Answer 1:

有几个方法可以做到这一点。 通常情况下,在框架(如果你看看通过反射源代码),你会看到类型参数的实例强制转换为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分配给它。 这是确定在这里做,因为它只是被每个,这是被称为类型调用一次
  • 如果默认值Tnull ,那么它假定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