通常检查null,不会在非约束类型上包含nullables。

时间:2010-10-22 18:22:47

标签: c# generics

假设我有以下方法:

public static int CountNonNullMembers<T>(this IEnumerable<T> enumerable)
{
    if (enumerable == null) throw new ArgumentNullException("enumerable");
    int count = 0;
    foreach (var x in enumerable)
    {
        if (x != null) count++; 
    }
    return count;
}

我有这3个数组::

var ints = Enumerable.Range(0,10).ToArray();
var nullableInts = Array.ConvertAll(ints,x=>x as int?);
var strings = Array.ConvertAll(ints,x => x.ToString());

我写了一个小函数来做一个循环并将它计时一百万次迭代。将其应用于intsstrings,它在我的机器上完成约100毫秒。对于nullableInts,需要2.5秒。 据我所知,int上的null检查没有意义,因此编译器为不可为空的struct类型提供了不同的模板,这将删除空检查。但Nullable<T>没有将空检查转换为x.HasValue的模板。如果我有一个无约束的函数,我怎么能做一个表现良好的空检查?我不能使用EqualityComparer<T>,因为null可能不是T的成员,因为没有约束。

也不可能有因约束而不同的重载,所以我不能说,structs有一个,Nullable<T>有一个,类有一个。

该方法的调用者是非约束的。这只是一个例子(不是实际的方法);调用方法是非约束的。我需要针对非null成员做一些工作,这是一种通用方法。我想我可以编写一个不执行检查的版本与执行该版本的版本(因此具有不同的签名),但它看起来非常难看且不需要。

此外,扩展方法.Count莫名其妙地为NullableIntsstrings执行,(同样糟糕),所以它确实不是正确的方法。这可能是委托调用,但我对此表示怀疑。使用UnboxT的{​​{1}}样式方法可以更好地执行。 好吧,非常奇怪的是将计数体转换为非常好的表现:

Check<T>.IfNull

为什么?

3 个答案:

答案 0 :(得分:1)

您可以constrain generic type parameters引用类型或值类型:

public static int CountNonNull<T>(this IEnumerable<T> source)
    where T : class
{
    return source.Count(x => x != null);
}

public static int CountNonNull<T>(this IEnumerable<Nullable<T>> source)
    where T : struct
{
    return source.Count(x => x.HasValue);
}

对于不可为空的结构,您不需要重载,因为无论如何它们都不能为空。

答案 1 :(得分:1)

使用UnboxT方法有效。但我也想要一些不需要创建静态类型的东西::

public static class Check<T>
{
            public static readonly Predicate<T> IfNull = CreateIfNullDelegate();
            private static bool AlwaysFalse(T obj)
            {
                return false;
            }

            private static bool ForRefType(T obj)
            {
                return object.ReferenceEquals(obj, null);
            }

            private static bool ForNullable<Tu>(Tu? obj) where Tu:struct
            {
                return !obj.HasValue;
            }
            private static Predicate<T> CreateIfNullDelegate()
            {
                if (!typeof(T).IsValueType)
                    return ForRefType;
                else
                {
                    Type underlying;
                    if ((underlying = Nullable.GetUnderlyingType(typeof(T))) != null)
                    {
                        return Delegate.CreateDelegate(
                            typeof(Predicate<T>),
                            typeof(Check<T>)
                                .GetMethod("ForNullable",BindingFlags.NonPublic | BindingFlags.Static)
                                    .MakeGenericMethod(underlying)
                        ) as Predicate<T>;
                    }
                    else
                    {
                        return AlwaysFalse;
                    }
                }
            }
        }

使用这种方法,一切都表现相同。 Strings表现更差,但并不比其他任何事情都差。

答案 2 :(得分:0)

虽然不一定比你的方法更好,但它不需要整个班级:

static Dictionary<Type, object> NullChecks = new Dictionary<Type, object>();
public static Func<T, bool> MakeNullCheck<T>()
{
    object obj;
    Func<T, bool> func;
    if (NullChecks.TryGetValue(typeof(T), out obj))
        return (Func<T, bool>)obj;
    if (typeof(T).IsClass)
        func = x => x != null;
    else if (Nullable.GetUnderlyingType(typeof(T)) != null)
    {
        var param = Expression.Parameter(typeof(T));
        func = Expression.Lambda<Func<T, bool>>(
            Expression.Property(param, typeof(T).GetProperty("HasValue")), param).Compile();
    }
    else
        func = x => false;
    NullChecks[typeof(T)] = func;
    return func;
}