Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Impure method is called for readonly field

Tags:

c#

resharper

I'm using Visual Studio 2010 + ReSharper and it shows a warning on the following code:

if (rect.Contains(point))
{
    ...
}

rect is a readonly Rectangle field, and ReSharper shows me this warning:

"Impure Method is called for readonly field of value type."

What are impure methods and why is this warning being shown to me?

like image 344
Acidic Avatar asked Mar 29 '12 14:03

Acidic


5 Answers

First off, Jon, Michael and Jared's answers are essentially correct but I have a few more things I'd like to add to them.

What is meant by an "impure" method?

It is easier to characterize pure methods. A "pure" method has the following characteristics:

  • Its output is entirely determined by its input; its output does not depend on externalities like the time of day or the bits on your hard disk. Its output does not depend on its history; calling the method with a given argument twice should give the same result.
  • A pure method produces no observable mutations in the world around it. A pure method may choose to mutate private state for efficiency's sake, but a pure method does not, say, mutate a field of its argument.

For example, Math.Cos is a pure method. Its output depends only on its input, and the input is not changed by the call.

An impure method is a method which is not pure.

What are some of the dangers of passing readonly structs to impure methods?

There are two that come to mind. The first is the one pointed out by Jon, Michael and Jared, and this is the one that ReSharper is warning you about. When you call a method on a struct, we always pass a reference to the variable that is the receiver, in case the method wishes to mutate the variable.

So what if you call such a method on a value, rather than a variable? In that case, we make a temporary variable, copy the value into it, and pass a reference to the variable.

A readonly variable is considered a value, because it cannot be mutated outside the constructor. So we are copying the variable to another variable, and the impure method is possibly mutating the copy, when you intend it to mutate the variable.

That's the danger of passing a readonly struct as a receiver. There is also a danger of passing a struct that contains a readonly field. A struct that contains a readonly field is a common practice, but it is essentially writing a cheque that the type system does not have the funds to cash; the "read-only-ness" of a particular variable is determined by the owner of the storage. An instance of a reference type "owns" its own storage, but an instance of a value type does not!

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

One thinks that this.x is not going to change because x is a readonly field and Badness is not a constructor. But...

S s = new S(1);
s.Badness(ref s);

... clearly demonstrates the falsity of that. this and s refer to the same variable, and that variable is not readonly!

like image 115
Eric Lippert Avatar answered Nov 03 '22 22:11

Eric Lippert


An impure method is one which isn't guaranteed to leave the value as it was.

In .NET 4, you can decorate methods and types with [Pure] to declare them to be pure, and R# will take notice of this. Unfortunately, you can't apply it to someone else's members, and you can't convince R# that a type/member is pure in a .NET 3.5 project as far as I'm aware. (This bites me in Noda Time all the time.)

The idea is that if you're calling a method which mutates a variable, but you call it on a read-only field, it's probably not doing what you want, so R# will warn you about this. For example:

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

This would be a really useful warning if every method which was actually pure was declared that way. Unfortunately they're not, so there are a lot of false positives :(

like image 30
Jon Skeet Avatar answered Nov 03 '22 21:11

Jon Skeet


The short answer is that this is a false positive, and you can safely ignore the warning.

The longer answer is that accessing a read-only value type creates a copy of it, so that any changes to the value made by a method would only affect the copy. ReSharper doesn't realize that Contains is a pure method (meaning it has no side effects). Eric Lippert talks about it here: Mutating Readonly Structs

like image 15
Michael Liu Avatar answered Nov 03 '22 20:11

Michael Liu


It sounds like ReSharper believes that the method Contains can mutate the rect value. Because rect is a readonly struct, the C# compiler makes defensive copies of the value to prevent the method from mutating a readonly field. Essentially, the final code looks like this:

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

ReSharper is warning you here that Contains may mutate rect in a way that would be immediately lost because it happened on a temporary.

like image 11
JaredPar Avatar answered Nov 03 '22 20:11

JaredPar


An Impure method is a method that could have side-effects. In this case, ReSharper seems to think it could change rect. It probably doesn't but the chain of evidence is broken.

like image 5
Henk Holterman Avatar answered Nov 03 '22 22:11

Henk Holterman