让我们考虑以下类定义:
public class MyClass<T>
{
public T t;
public bool? c1(T obj) => obj?.Equals(null);
public bool? c2() => t?.Equals(null);
}
毕竟,一些注意事项:
-
MyClass<T>
不对T
类型施加任何约束 - 因此,T
可以是class
或struct
; -
c2() == c1(t)
必须始终正确。 - 我使用 http://tryroslyn.azurewebsites.net/站点编译一些代码片段,看看 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
版本中,Equals
在obj
的副本上被调用。在c2
版本中,必须格外小心地在t
上调用Equals
,而不是t
的副本。这是因为T
可能是一个值类型,它已被重写Equals
以修改其自己的实例数据。由于您在t
上调用Equals
,因此修改应该在t
中可见。
优化对c1
来说只是因为任何人都无法在方法返回后检查obj
,因此无论是obj
还是可能被修改的obj
副本都无关紧要。