Reputation: 7577
Let's say I have the following method:
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;
}
And I have these 3 arrays::
var ints = Enumerable.Range(0,10).ToArray();
var nullableInts = Array.ConvertAll(ints,x=>x as int?);
var strings = Array.ConvertAll(ints,x => x.ToString());
I wrote a little function to do a loop and time it for a million iterations. Applying it to ints
and strings
, it finishes in about 100 ms on my machine. For nullableInts
, it takes 2.5 seconds.
As I understand the check for null on int
doesn't make sense, so the compiler has a different template for non-nullable struct
types, that removes null checks. But Nullable<T>
does not have a template that converts the null check to x.HasValue
. If I have an unconstrained function, how can I do a null check that will perform well? I can't use EqualityComparer<T>
, as null might not be a member of T
as there is no constraint.
Also it's impossible to have overloads that differ by constraint, so I can't, say, have one for structs
, one for Nullable<T>
, and one for classes.
The caller of the method is non-constrained. This is just an example (not the actual method); the method calling is non-constrained. I need to do some work against non-null members, and it's a generic method. I suppose I could write a version that doesn't do the check vs one that does (and consequently has a different signature), but it's seems very ugly and unneeded.
Also, the extension method .Count
inexplicably performs horribly for NullableInts
and strings
, (equally bad), so it really isn't the right approach. This might be the delegate invocation, but I doubt it. Using the UnboxT
style method of Check<T>.IfNull
performs a lot better.
Okay, really weird switching the body of the count to this performs great:
public static int CountNonNullMembers<T>(this IEnumerable<T> enumerable)
{
return enumerable.Count(Check<T>.IfNull.Invoke);
}
Why?
Upvotes: 3
Views: 228
Reputation: 86718
While not necessarily better than your method, it doesn't require a whole class:
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;
}
Upvotes: 0
Reputation: 7577
Using the UnboxT
approach works. But I'd also like something that doesn't require creating a static type::
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;
}
}
}
}
Using this approach everything performs about the same. Strings
performs worse, but not so much worse than everything else.
Upvotes: 1
Reputation: 217313
You can constrain generic type parameters to reference types or values types:
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);
}
You don't need an overload for non-nullable structs, because they can't be null anyway.
Upvotes: 1