罗斯林 C#6?运算符编译在泛型中看起来很乱

Roslyn C#6 ?. operator compilation looks messy in generic types

让我们考虑以下 class 定义:

public class MyClass<T>
{
    public T t;
    public bool? c1(T obj) => obj?.Equals(null);
    public bool? c2() => t?.Equals(null);
}

毕竟有些注意事项:

现在,让我们分析一下Roslyn是如何编译的MyClass<T>:

1.) c1(T) 案例:

没想到,通过Roslyn编译器验证生成的代码后,我们可以看到如下:

public bool? c1(T obj)
{
    return obj != null ? new bool?(obj.Equals(null)) : null;
}

2.) c2() 案例:

我期望的是与 c1(T) 相同的代码。但是,我看到的是:

public unsafe bool? c2()
{
    T* arg_33_0 = ref this.t;
    T t = default(T);
    bool? arg_43_0;
    if (t == null)
    {
        t = this.t;
        arg_33_0 = ref t;
        if (t == null)
        {
            arg_43_0 = null;
            return arg_43_0;
        }
    }
    arg_43_0 = new bool?(arg_33_0.Equals(null));
    return arg_43_0;
}

哇,为什么会发出所有不必要的代码?在发布编译模式下,我们可以看到C1 有39 字节 代码大小,而C2 方法有68 字节。这是可以优化的东西吗?

对于 c2 的情况,c1 CIL 代码是错误的。

c1 版本中,Equalsobj 的副本上被调用。在 c2 版本中,必须格外小心地调用 t 上的 Equals,而不是 t 的副本。那是因为 T 可能是一个值类型,它覆盖了 Equals 以修改它自己的实例数据。由于您在 t 上调用 Equals,所以修改应该在 t.

中可见

只有c1 可以优化 因为在方法返回后任何人都无法检查obj,所以它不会无论是 obj 还是 obj 的副本都可能被修改。