【问题标题】:How to check if a generic parameter is not null without boxing如何在没有装箱的情况下检查泛型参数是否不为空
【发布时间】:2017-11-21 22:33:04
【问题描述】:

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

obj 为空时抛出:

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 是类还是结构:

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 和旧标题标记为重复。所以我更新了标题以更好地表达我的问题。我要问的是,我怎样才能调用泛型方法并使用参数只有在它不为空时才使用,而不是装箱。

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

【问题讨论】:

  • 只有引用类型(类)可以为空。值类型(结构)永远不能为空(并且为Nullable&lt;T&gt; 使用空只是语法糖)。另一方面,为了调用实例方法,可以对结构进行装箱;请参阅stackoverflow.com/questions/5494807 进行讨论。

标签: c# generics interface overloading boxing


【解决方案1】:

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

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

is 操作的结果总是false 如果obj 为null 或值为null 的可空类型,则检查是否从T 到@987654327 的装箱转换@ 存在,总是如此。

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

【讨论】:

  • obj is object 由编译器通过obj != null 优化(这是一个自然的选择),并且由于 T 没有任何约束,因此在检查发生之前会发生装箱,因此该命题无效
【解决方案2】:

我想出了这个助手:

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

【讨论】:

  • 拳击有什么问题?
  • 我问是因为我仍然在该三元表达式的 false 分支中看到一个框指令。
  • 本例中没有任何内容,但如果方法被大量调用(例如在紧密循环中),则不希望进行不必要的分配。
  • 我怀疑它是在 IsNull 字段的初始化中(一次性),而不是在每次调用 Foo 时?编辑:抱歉,错误分支是针对引用类型的,因此不会导致分配错误。
猜你喜欢
  • 2022-11-03
  • 2022-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多