对于只读字段调用Impure方法

Aci*_*dic 81 c# resharper

我正在使用Visual Studio 2010 + Resharper并在以下代码中显示警告:

if (rect.Contains(point))
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

rect是一个readonly Rectangle领域,Resharper向我展示了这个警告:

"为值只读的字段调用Impure方法."

什么是不纯的方法,为什么这个警告会显示给我?

Eri*_*ert 95

首先,Jon,Michael和Jared的回答基本上是正确的,但我还有一些我想添加的东西.

什么是"不纯"的方法?

表征纯方法更容易."纯"方法具有以下特征:

  • 它的输出完全取决于它的输入; 它的输出不依赖于外部因素,如一天中的时间或硬盘上的位.它的输出并不取决于它的历史; 使用给定参数两次调用该方法应该给出相同的结果.
  • 纯方法在其周围的世界中不会产生可观察到的突变.为了效率,纯方法可以选择改变私有状态,但是纯方法不会改变其参数的字段.

例如,Math.Cos是一种纯方法.其输出仅取决于其输入,并且调用不会更改输入.

不纯的方法是一种不纯粹的方法.

将只读结构传递给不纯方法有哪些危险?

有两个想到的.第一个是Jon,Michael和Jared指出的那个,这是Resharper警告你的那个.当你在一个struct上调用一个方法时,我们总是传递一个对接收者变量的引用,以防该方法希望改变变量.

那么如果你在值上调用这样的方法而不是变量呢?在这种情况下,我们创建一个临时变量,将值复制到其中,并传递对该变量的引用.

readonly变量被认为是一个值,因为它不能在构造函数之外变异.所以我们将变量复制到另一个变量,当你打算改变变量时,不纯的方法可能会改变副本.

这就是将readonly结构作为接收器传递的危险.传递包含只读字段的结构也存在危险.包含只读字段的结构是一种常见的做法,但它实质上是在检查类型系统没有资金来支付现金; 特定变量的"只读"由存储的所有者确定.引用类型的实例"拥有"其自己的存储,但值类型的实例不支持!

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);   
  }
}
Run Code Online (Sandbox Code Playgroud)

有人认为这this.x不会改变,因为x是一个只读字段而Badness不是构造函数.但...

S s = new S(1);
s.Badness(ref s);
Run Code Online (Sandbox Code Playgroud)

......清楚地表明了这种虚假.thiss引用相同的变量,变量不是只读的!

  • 即使方法无效,Resharper也会发出此警告,除了`return`之外什么都不做.基于此,我猜测唯一的标准是该方法是否具有`[Pure]`属性. (3认同)

Jon*_*eet 51

不纯的方法是不保证保留原样的方法.

在.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
    }
}
Run Code Online (Sandbox Code Playgroud)

如果每个实际上纯粹的方法都以这种方式声明,那么这将是一个非常有用的警告.不幸的是他们不是,所以有很多误报:(

  • @JulienLebosquain:我真的*不愿意开始添加特定于工具的注释 - 特别是对于一个开源项目.很高兴知道作为一种选择,但...... (5认同)
  • 您可以使用`JetBrains.Annotations.PureAttribute`而不是`System.Diagnostics.Contracts.PureAttribute`,它们与ReSharper代码分析具有相同的含义,并且应该在.NET 3.5,.NET 4或Silverlight上同样有效.您还可以使用XML文件在外部注释您不拥有的程序集(查看ReSharper bin路径中的ExternalAnnotations目录),它确实非常有用! (2认同)

Mic*_*Liu 15

简短的回答是这是误报,你可以放心地忽略警告.

更长的答案是访问只读值类型会创建它的副本,因此方法所做的值的任何更改都只会影响副本.ReSharper没有意识到这Contains是一种纯粹的方法(意味着它没有副作用).Eric Lippert在这里谈到它:变异Readonly Structs

  • 在完全理解之前,请不要忽略此警告!!!一个可以使您放心的好例子是以下结构:`private readonly SpinLock _spinLock = new SpinLock();`-这样的锁将是完全无用的(因为readonly修饰符会导致每次Enter方法都创建即时复制)被称为) (2认同)

Jar*_*Par 11

听起来Reshaprer认为该方法Contains可以改变rect价值.因为rectreadonly structC#编译器制作值的防御副本以防止该方法变异readonly字段.基本上最终的代码看起来像这样

Rectangle temp = rect;
if (temp.Contains(point)) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

Resharper在这里警告你Contains可能会rect以一种会立即丢失的方式发生变异,因为它发生在一个临时的.


Hen*_*man 5

Impure方法是一种可能产生副作用的方法.在这种情况下,Resharper似乎认为它可能会改变rect.它可能没有,但证据链被打破了.