我正在使用Visual Studio 2010 + Resharper,它在以下代码上显示警告:
if (rect.Contains(point))
{
...
}
rect
是一个readonly Rectangle
字段,Resharper向我显示此警告:
“为值类型的只读字段调用了Immpure方法。”
什么是不正确的方法?为什么向我显示此警告?
Answers:
首先,乔恩(Jon),迈克尔(Michael)和贾里德(Jared)的答案本质上是正确的,但我想补充一些其他内容。
什么是“不纯”方法?
表征纯方法更容易。“纯”方法具有以下特征:
例如,Math.Cos
是一种纯方法。其输出仅取决于其输入,并且输入不会被调用更改。
不纯方法是不纯净的方法。
将只读结构传递给不纯方法有哪些危险?
我想到了两个。第一个是乔恩(Jon),迈克尔(Michael)和贾里德(Jared)指出的那个,而这也是Resharper警告您的那个。当您在结构上调用方法时,我们总是将引用传递给作为接收方的变量,以防该方法希望对该变量进行突变。
那么,如果您对一个值而不是一个变量调用这样的方法呢?在这种情况下,我们将创建一个临时变量,将值复制到其中,然后将引用传递给该变量。
只读变量被认为是一个值,因为它不能在构造函数之外进行突变。因此,我们正在将变量复制到另一个变量,并且当您打算使变量变异时,不纯方法可能会使副本变异。
这是将只读结构作为接收器传递的危险。还存在传递包含只读字段的结构的危险。包含只读字段的结构是一种常见的做法,但实际上是在写一张检查,检查类型系统没有现金来兑现。特定变量的“只读性”由存储的所有者确定。引用类型的实例“拥有”自己的存储空间,而值类型的实例则没有!
struct S
{
private readonly int x;
public S(int x) { this.x = x; }
public void Badness(ref S s)
{
Console.WriteLine(this.x);
s = new S(this.x + 1);
// This should be the same, right?
Console.WriteLine(this.x);
}
}
有人认为这this.x
不会改变,因为x是一个只读字段而Badness
不是构造函数。但...
S s = new S(1);
s.Badness(ref s);
……清楚地证明了这一事实。this
并s
引用相同的变量,并且该变量不是只读的!
struct Id {
private readonly int _id;
public Id(int id) { _id = id; }
public int ToInt() => _id;
}
为什么ToInt不纯?
return
。基于此,我猜测唯一的标准是该方法是否具有[Pure]
属性。
rect
。我们是说将的副本rect
传递给Contains
method吗?
不纯净的方法不能保证将其原样保留。
在.NET 4中,您可以使用来修饰方法和类型,[Pure]
以将其声明为纯净的,R#会注意到这一点。不幸的是,您不能将其应用于其他人的成员,并且据我所知,您不能说服R#类型/成员在.NET 3.5项目中是纯净的。(这一直在Noda Time咬我。)
这个想法是,如果您要调用一个使变量变异的方法,但是您在一个只读字段上调用它,则可能未达到所需的目的,因此R#会就此警告您。例如:
public struct Nasty
{
public int value;
public void SetValue()
{
value = 10;
}
}
class Test
{
static readonly Nasty first;
static Nasty second;
static void Main()
{
first.SetValue();
second.SetValue();
Console.WriteLine(first.value); // 0
Console.WriteLine(second.value); // 10
}
}
如果实际上以纯方法声明了每个方法,这将是一个非常有用的警告。不幸的是它们不是,所以存在很多误报:(
JetBrains.Annotations.PureAttribute
代替System.Diagnostics.Contracts.PureAttribute
ReSharper代码分析,它们具有相同的含义,并且应在.NET 3.5,.NET 4或Silverlight上均能正常工作。您还可以使用XML文件在外部注释不属于您的程序集(请查看ReSharper bin路径中的ExternalAnnotations目录),它确实非常有用!
System.Diagnostics.Contracts.PureAttribute
在R#8.2中并没有抑制该警告,而JetBrains.Annotations.PureAttribute
确实如此。这两个属性也有不同的描述:ContractsPure
属性表示“结果仅取决于参数”,而JetBrainsPure
表示“不会导致可见状态改变”,而不排除用于计算结果的对象状态。(但是仍然Pure
没有对这个警告产生相同影响的合同可能是一个错误。)
简短的答案是,这是一个误报,您可以放心地忽略该警告。
更长的答案是,访问只读值类型会创建它的副本,因此对方法所做的值所做的任何更改都只会影响该副本。ReSharper并未意识到这Contains
是一种纯粹的方法(这意味着它没有副作用)。埃里克·利珀特(Eric Lippert)在这里谈论它:变异只读结构
private readonly SpinLock _spinLock = new SpinLock();
-这样的锁将是完全无用的(因为readonly修饰符会导致每次调用Enter方法时都创建即时复制)