87

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?

Pang
  • 9,564
  • 146
  • 81
  • 122
Acidic
  • 6,154
  • 12
  • 46
  • 80
  • http://www.minddriven.de/index.php/technology/dot-net/code-contracts/code-contracts-method-purity – siride Mar 29 '12 at 14:31

5 Answers5

97

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!

Pang
  • 9,564
  • 146
  • 81
  • 122
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Fair enought, but please consider this code: `struct Id { ` `private readonly int _id;` `public Id(int id) { _id = id; }` `public int ToInt() => _id;` `}` Why is *ToInt* impure? – boskicthebrain Aug 14 '17 at 12:15
  • @boskicthebrain: Is your question actually "why does Resharper consider this to be impure?" If that's your question then find someone who works on R# and ask them! – Eric Lippert Aug 15 '17 at 20:01
  • 3
    Resharper will give this warning even if the method is void and does nothing except `return`. Based on that, I'm guessing that the only criteria is whether or not the method has the `[Pure]` attribute. – bornfromanegg Dec 14 '17 at 09:37
51

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 :(

Pang
  • 9,564
  • 146
  • 81
  • 122
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • So that means that an impure method might change the underlying fields of the mutable valuetype passed to it? – Acidic Mar 29 '12 at 14:37
  • @Acidic: Not the argument value - even an impure method can do that - but the value you *call it on*. (See my example, where the method doesn't even have any parameters.) – Jon Skeet Mar 29 '12 at 14:38
  • Does that mean that any value type that `readonly` becomes immutable? – Acidic Mar 29 '12 at 14:40
  • 2
    You can use `JetBrains.Annotations.PureAttribute` instead of the `System.Diagnostics.Contracts.PureAttribute`, they have the same meaning for ReSharper code analysis and should work equally on .NET 3.5, .NET 4 or Silverlight. You can also externally annotate assemblies you don't own using XML files (have a look at the ExternalAnnotations directory in ReSharper bin path), it can really be quite useful! – Julien Lebosquain Mar 29 '12 at 19:38
  • 5
    @JulienLebosquain: I'd be *really* reluctant to start adding tool-specific annotations - especially for an open source project. Good to know about as an options, but... – Jon Skeet Mar 29 '12 at 19:40
  • Upvoted for the concise answer and a great example. Thanks. :) – Mal Ross Oct 19 '12 at 08:44
  • 1
    Actually I found that `System.Diagnostics.Contracts.PureAttribute` didn't suppress this warning in R# 8.2, while `JetBrains.Annotations.PureAttribute` did. The two attributes also have different descriptions: The contracts `Pure` attribute implies "result depends only on parameters", while the JetBrains `Pure` implies "doesn't cause visible state changes" without excluding the object state being used to calculate the result. (But still contracts `Pure` not having the same effect on this warning probably is a bug.) – Wormbo Jul 18 '14 at 06:12
  • @Wormbo: I'm not sure which contracts documentation you're referring to, but http://msdn.microsoft.com/en-us/library/system.diagnostics.contracts.pureattribute.aspx is described as "Indicates that a type or method is pure, that is, it does not make any visible state changes." - I don't see anything about the result only depending on parameters. – Jon Skeet Jul 18 '14 at 06:31
  • Regardless of ReSharper warnings, does any attribute affect the compiler and avoid the defensive copy? Please don't tell me the compiler is smart enough to do this anyways - unfortunately, this is not true, even for simple cases. Learned it the hard way. – Patrick Stalph Oct 01 '19 at 08:48
  • @PatrickStalph: The compiler definitely doesn't use the JetBrains attributes. But now in C# 7 you can create a `readonly struct`, at which point it *does* avoid the defensive copy. That's specified via an attribute too (`IsReadOnlyAttribute`) so yes, there are attributes that change what the compiler does. C# 8 also allows for `readonly` method declarations; I haven't yet checked whether that avoids the copy, but I'd hope it does. – Jon Skeet Oct 01 '19 at 09:13
  • Thanks @JonSkeet for pointing to the `IsReadOnlyAttribute` https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.isreadonlyattribute?view=netstandard-2.1 which is an interesting alternative to `readonly struct`. I'd like to add that the keyword is bound to C#7.2 while the attribute is only available for netstandard2.1. – Patrick Stalph Oct 02 '19 at 05:01
15

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

Pang
  • 9,564
  • 146
  • 81
  • 122
Michael Liu
  • 52,147
  • 13
  • 117
  • 150
  • 2
    Please never ignore this warning until fully understood!!! One good example where this can safe you is this construct: `private readonly SpinLock _spinLock = new SpinLock();` - such a lock would be completely useless (since readonly modifier causes on-the-fly copy to be created each time Enter method is called on it) – Jan Jul 31 '15 at 15:09
11

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • So that wouldn't impact any logic performed in the method, only prevent it from mutating the value that it was called upon, right? – Acidic Mar 29 '12 at 14:44
5

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
H H
  • 263,252
  • 30
  • 330
  • 514