只读字段调用了不纯方法


Answers:


96

首先,乔恩(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);

……清楚地证明了这一事实。thiss引用相同的变量,并且变量不是只读的!


足够公平,但是请考虑以下代码: struct Id { private readonly int _id; public Id(int id) { _id = id; } public int ToInt() => _id; } 为什么ToInt不纯?
boskicthebrain

@boskicthebrain:您的问题实际上是“为什么Resharper认为这是不纯的?” 如果这是您的问题,请找一个从事R#的人问他们!
埃里克·利珀特

3
即使该方法无效,Resharper也会发出此警告,除了之外什么也没有做return。基于此,我猜测唯一的标准是该方法是否具有[Pure]属性。
–bornfromanegg

我发现这句话“我们总是将引用传递给作为接收者的变量”,这让我有些困惑。在采购订单中,“变量”指的是什么?我认为这是rect。我们是说将的副本rect传递给Containsmethod吗?
xtu

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
    }
}

如果实际上以纯方法声明了每个方法,这将是一个非常有用的警告。不幸的是它们不是,所以存在很多误报:(


因此,这意味着不纯净的方法可能会更改传递给它的可变值类型的基础字段?
酸性2012年

@Acidic:不是参数值-即使是不纯方法也可以做到-但要在其上调用的值。(请参阅我的示例,其中该方法甚至没有任何参数。)
Jon Skeet 2012年

2
您可以使用它们JetBrains.Annotations.PureAttribute代替System.Diagnostics.Contracts.PureAttributeReSharper代码分析,它们具有相同的含义,并且应在.NET 3.5,.NET 4或Silverlight上均能正常工作。您还可以使用XML文件在外部注释不属于您的程序集(请查看ReSharper bin路径中的ExternalAnnotations目录),它确实非常有用!
朱利安·勒博斯奎因

5
@JulienLebosquain:我真的很不愿意开始添加特定于工具的注释-特别是对于开源项目。很高兴知道作为一个选择,但是...
Jon Skeet 2012年

1
实际上,我发现System.Diagnostics.Contracts.PureAttribute在R#8.2中并没有抑制该警告,而JetBrains.Annotations.PureAttribute确实如此。这两个属性也有不同的描述:ContractsPure属性表示“结果仅取决于参数”,而JetBrainsPure表示“不会导致可见状态改变”,而不排除用于计算结果的对象状态。(但是仍然Pure没有对这个警告产生相同影响的合同可能是一个错误。)
Wormbo 2014年

15

简短的答案是,这是一个误报,您可以放心地忽略该警告。

更长的答案是,访问只读值类型会创建它的副本,因此对方法所做的值所做的任何更改都只会影响该副本。ReSharper并未意识到这Contains是一种纯粹的方法(这意味着它没有副作用)。埃里克·利珀特(Eric Lippert)在这里谈论它:变异只读结构


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

11

听起来Reshaprer相信该方法Contains可以改变rect值。因为rectreadonly structC#编译器制作值的防御性副本,以防止方法使readonly字段发生变异。本质上,最终代码如下所示

Rectangle temp = rect;
if (temp.Contains(point)) {
  ...
}

Resharper在此警告您,Contains可能会rect以某种方式发生变异,因为它是临时发生的,因此会立即丢失。


这样就不会影响该方法中执行的任何逻辑,只会阻止它使被调用的值发生变化,对吗?
酸性

5

不纯方法是可能有副作用的方法。在这种情况下,Resharper似乎认为它可能会改变rect。可能没有,但是证据链断裂了。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.