0

I want to write a method that accepts a single IBar and call its Baz method.

This throws when obj is null:

void Foo<T>(T obj)
    where T : IBar
    => obj.Baz();

This boxes obj when it is a value type:

void Foo<T>(T obj)
    where T : IBar
    => obj?.Baz();

This doesn't call Baz if obj is zero:

void Foo<T>(T obj)
    where T : IBar
{
    if (!EqualityComparer<T>.Default.Equals(obj, default(T)))
        obj.Baz();
}

And here, Foo(new Bar()) always picks the generic method no matter if Bar is a class or struct:

void Foo<T>(T obj)
    where T : struct, IBar
    => obj.Baz();

void Foo(IBar obj)
    => obj?.Baz();

This makes my eyes bleed:

void FooVal<T>(T obj)
    where T : struct, IBar
    => obj.Baz();

void FooRef(IBar obj)
    => obj?.Baz();

So is there a best practice for this? I'm open to all suggestions.

Edit:

The question is marked as duplicate with Generic constraints, where T : struct and where T : class and with the old title, it was. So I've updated the title to convey my problem better. What I'm asking is that how can I call a generic method and use the argument only if it is not null, without boxing.

Some workarounds explained for the linked question may be used to answer this one but I still believe this is a fundamentally different question.

Şafak Gür
  • 7,045
  • 5
  • 59
  • 96
  • Only a reference type (class) can ever be null. A value type (struct) can never be null (and using null for ``Nullable`` is just syntactic sugar). On the other hand, a struct may be boxed for the purpose of invoking an instance method; see https://stackoverflow.com/questions/5494807 for a discussion. – dumetrulo Nov 22 '17 at 09:44

2 Answers2

2

Having just had a similar problem, I came up with the following solution:

void Foo<T>(T obj)
{
  if (obj is object)
    obj.Baz();
}

The result of the is operation is always false if obj is null or a nullable type with a value of null, and if not it checks whether a boxing conversion from T to object exists, which is always the case.

Unless the compiler is not optimizing at all, this should in effect end up being just a null check, or a noop for non-nullable types.

Leak
  • 175
  • 2
  • 6
  • `obj is object` is optimised by the compiler by `obj != null` (which is a natural choice) and because T does not have any constraint, a boxing occurs before the check happens hence invalidating this proposition – gfache Apr 25 '20 at 23:28
1

I came up with this helper:

public static class Null
{
    public static bool IsNull<T>(T obj) => !Data<T>.HasValue(obj);

    private static class Data<T>
    {
        public static readonly Func<T, bool> HasValue = InitHasValue();

        private static Func<T, bool> InitHasValue()
        {
            var type = typeof(T);
            if (!type.IsValueType)
                return obj => obj != null;

            if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                var value = Expression.Parameter(type, "value");
                var getter = type.GetProperty("HasValue").GetMethod;
                var call = Expression.Call(value, getter);
                var lambda = Expression.Lambda<Func<T, bool>>(call, value);
                return lambda.Compile();
            }

            return obj => true;
        }
    }
}

So I can do this:

void Foo<T>(T obj)
    where T : IBar
{
    if (!Null.IsNull(obj)) // Does not box.
        obj.Baz();
}
Şafak Gür
  • 7,045
  • 5
  • 59
  • 96