为什么这种方法不纯?

时间:2013-03-25 03:40:59

标签: c# .net

我读到了这个答案:https://stackoverflow.com/a/9928643/16241

但我显然不理解它,因为我无法弄清楚为什么我的方法不纯。 (有问题的方法是ToExactLocation())。

public struct ScreenLocation
{
    public ScreenLocation(int x, int y):this()
    {
        X = x;
        Y = y;
    }

    public int X { get; set; }
    public int Y { get; set; }

    public ExactLocation ToExactLocation()
    {
        return new ExactLocation {X = this.X, Y = this.Y};
    }

    // Other stuff
}

这里你需要的是确切的位置结构:

public struct ExactLocation
{
    public double X { get; set; }
    public double Y { get; set; }

    // Various Operator Overloads, but no constructor
}

这就是我所说的:

someScreenLocation = MethodThatGivesAScreenLocation();
if (DestinationLocation == someScreenLocation.ToExactLocation())
{
     // Do stuff
}

当我这样做时,ReSharper用"Impure Method is called for readonly field of value type."

标记它

为什么这么说?我该怎么办才能让它消失?

6 个答案:

答案 0 :(得分:8)

它不纯,因为它不会返回仅依赖于其输入的值。当XY的值发生变化时,ToExactLocation的返回值也会发生变化,即其输出取决于内部的可变状态。

此外,X 中的YExactLocation的设置者可能会改变输入。 ScreenLocation的吸气者也可能。

someScreenLocation是一个只读字段,是一种值类型。您在值上调用ToExactLocation,即只读字段。当您访问reaodnly值类型时,将创建一个副本,以避免改变值本身。但是,您的调用可能会改变该值,在许多情况下,这不是您想要的,因为您将改变副本。这就是你收到警告的原因。

在这种情况下,你可以忽略它,但我会避免一般的可变值类型。

编辑:

让我试着简化......

struct Point
{
    int X;
    int Y;
    bool Mutate() { X++; Y++; }
}

class Foo
{
    public readonly Point P;
    Foo() 
    { 
        P = new Point();
        P.Mutate();  // impure function on readonly value type
    }
}

调用Mutate()时,会创建P的副本并将其与方法一起传递。 P内部状态的任何突变都是无关紧要的,因为它会改变副本。

答案 1 :(得分:6)

Pure Method 的一个条件是它的输出(返回值)完全取决于它的输入(参数)。

您的.ToExactLocation()方法不是纯,因为它的输出既取决于输入参数,也取决于可变结构的当前值。

Resharper不喜欢这样,因为可变结构很糟糕(不要使用它们)。我希望如果您更改代码以使用类而不是结构或重新设计结构,则错误将消失,因此.X和.Y成员只能由构造函数设置。

答案 2 :(得分:3)

阅读答案后,我发现pure函数必然与数学函数相似。如果f(x) = x^2 + 2x + 10为0,则10始终返回x

因此ToExactLocation()每次调用时都必须返回相同的值,无论对象自初始创建后是否更改,都将其称为“纯”。

答案 3 :(得分:2)

“纯函数”有两个含义:一个theoretical(没有副作用/不依赖于可变状态),另一个是ReSharper对函数的看法。

从理论的角度来看,你的功能并不纯粹,因为它取决于可变状态。样品:

var someScreenLocation = new ScreenLocation(1,1);

var locationOne = someScreenLocation.ToExactLocation();
var locationTwo = someScreenLocation.ToExactLocation();
someScreenLocation.X = 3;
var locationThree = someScreenLocation.ToExactLocation();

对于纯粹的方法,它可以根据输入改变其结果 (完全没有,因为在这种情况下没有参数)。但是你可以清楚地看到locationOnelocationTwo是相同的(到目前为止是好的符号),但不幸的是locationThree是不同的,即使输入(函数的参数)仍然相同。

您可以通过制作XY readonly(以及添加构造函数)使其在理论上变得纯粹。

即使在更改后,ReSharper仍然认为它不纯粹 - 说服它你可以使用Pure属性将其标记为纯粹。

请注意,即使在具有readonly字段的类的构造函数中,ReSharper也会标记“不纯”函数的使用。下面的示例显示了ReSharper警告:

struct Point
{
    public int X;
    public int Y;
    public Point(int x, int y){X = x;Y = y;}

    public void Mutate(){X++;}
    public Point TheoreticallyPure(){return new Point(1, 1);}
    [Pure] public Point MarkedPure(){ return new Point(1, 1);}
}

class WithReadonlyField
{
    public readonly Point P;
    public WithReadonlyField()
    {
        P = new Point();
        P.TheoreticallyPure();  // impure function on readonly value type
        P.MarkedPure(); // return value of pure not used
        P.Mutate();   // impure function on readonly value type - modifies P.
        P = new Point().MarkedPure(); // ok to modify P multiple times.
    }
    public void NormalMethod()
    {
        P.Mutate();   // impure function on readonly value type, no changes to P
    }
}

C#允许修改readonly字段直到构造函数的末尾,但ReSharper也标记了所有“不纯”函数的用法(注意构造函数中的Mutate函数实际上更改了{{1}的值} {field} readonly,与P不同,它不起作用。)

  

readonly ...声明引入的字段的赋值只能作为声明的一部分或在同一个类的构造函数中出现”

ReSharper的这种行为很可能是为了保持一致性,并避免移动完全有效的代码完全改变行为的情况。

答案 4 :(得分:1)

最好将其建模为静态方法(在任一类上),并且可以消除不纯的警告。解释省略,因为其他答案涵盖了原因。

示例:

public static ExactLocation ToExactLocation(ScreenLocation loc)
{
    return new ExactLocation {X = loc.X, Y = loc.Y};
}

或使用扩展方法

public static ExactLocation ToExactLocation(this ScreenLocation loc)
{
    return new ExactLocation {X = loc.X, Y = loc.Y};
}

答案 5 :(得分:0)

不确定原因,如果格式正确,我会把它作为评论...

你不想要这样的东西:

var someScreenLocation = MethodThatGivesAScreenLocation();
if (DestinationLocation.X == someScreenLocation.ToExactLocation().X &&
    DestinationLocation.Y == someScreenLocation.ToExactLocation().Y)
{
     // Do stuff
}