如何在不装箱的情况下检查泛型参数是否不为空

How to check if a generic parameter is not null without boxing

我想编写一个接受单个 IBar 并调用其 Baz 方法的方法。

obj 为 null 时抛出:

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

当它是值类型时,此框 obj

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

如果 obj 为零,则不会调用 Baz

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

在这里,Foo(new Bar()) 总是选择通用方法,无论 Bar 是 class 还是结构:

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

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

这让我眼睛流血:

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

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

那么对此有最佳实践吗?我愿意接受所有建议。

编辑:

该问题被标记为 Generic constraints, where T : struct and where T : class 重复,并且使用的是旧标题。所以我更新了标题以更好地传达我的问题。我要问的是,如何调用泛型方法并仅在参数不为 null 时才使用参数 ,无需装箱。

可以使用针对链接问题 解释的一些解决方法 来回答这个问题,但我仍然认为这是一个根本不同的问题。

我想出了这个帮手:

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;
        }
    }
}

所以我可以这样做:

void Foo<T>(T obj)
    where T : IBar
{
    if (!Null.IsNull(obj)) // Does not box.
        obj.Baz();
}

刚遇到类似的问题,我想到了以下解决方案:

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

如果 obj 为 null 或值为 null 的可空类型,则 is 操作的结果始终为 false,如果不是,则检查是否存在从 Tobject 的装箱转换,情况总是如此。

除非编译器根本没有优化,否则这实际上应该只是一个 null 检查,或者是对不可空类型的 noop。