Метод Impure вызывается для поля readonly

Я использую Visual Studio 2010 + Resharper, и он показывает предупреждение по следующему коду:

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

rect - это поле readonly Rectangle, и Resharper показывает мне это предупреждение:

"Метод Impure вызывается для поля readonly типа значения."

Что такое нечистые методы и почему это предупреждение показано мне?

Ответ 1

Во-первых, ответы Джона, Майкла и Джареда в основном правильны, но у меня есть еще несколько вещей, которые я хотел бы добавить к ним.

Что подразумевается под "нечистым" методом?

Легче охарактеризовать чистые методы. "Чистый" метод имеет следующие характеристики:

  • Его вывод полностью определяется его входом; его выход не зависит от внешних факторов, таких как время суток или бит на жестком диске. Его выход не зависит от его истории; вызов метода с заданным аргументом дважды должен дать тот же результат.
  • Чистый метод не производит наблюдаемых мутаций в окружающем его мире. Чистый метод может выбирать для того, чтобы мутировать частное состояние для эффективности, но чистый метод, скажем, не изменяет поле его аргумента.

Например, Math.Cos - это чистый метод. Его выход зависит только от его ввода, и вход не изменяется при вызове.

Нечистый метод - это метод, который не является чистым.

Каковы некоторые из опасностей передачи readonly-структур на нечистые методы?

Есть два, которые приходят на ум. Первый - тот, который указал Джон, Майкл и Джаред, и это тот, о котором предупреждает вас Решар. Когда вы вызываете метод в struct, мы всегда передаем ссылку на переменную, которая является получателем, в случае, если метод хочет изменить эту переменную.

Итак, что, если вы вызываете такой метод по значению, а не по переменной? В этом случае мы создаем временную переменную, копируем в нее значение и передаем ссылку на переменную.

Переменная readonly считается значением, поскольку она не может быть изменена вне конструктора. Таким образом, мы копируем переменную в другую переменную, а нечистый метод, возможно, мутирует копию, когда вы намерены изменить ее.

Это опасность передачи структуры readonly как получателя. Существует также опасность передачи структуры, содержащей поле 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);   
  }
}

Кто-то думает, что this.x не изменится, потому что x является полем только для чтения, а Badness не является конструктором. Но...

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

... наглядно демонстрирует ложность этого. this и s относятся к одной и той же переменной, и эта переменная не является readonly!

Ответ 2

Нечистый метод - это тот, который не гарантирует, что значение останется таким, каким оно было.

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

Это было бы действительно полезным предупреждением, если бы каждый метод, который был фактически чистым, был объявлен таким образом. К сожалению, это не так, поэтому есть много ложных срабатываний: (

Ответ 3

Короткий ответ заключается в том, что это ложное срабатывание, и вы можете смело игнорировать это предупреждение.

Более длинный ответ заключается в том, что доступ к типу значений только для чтения создает его копию, так что любые изменения значения, сделанные методом, повлияют только на копию. ReSharper не понимает, что Contains является чистым методом (это означает, что он не имеет побочных эффектов). Эрик Липперт говорит об этом здесь: Мутирование Readonly Structs

Ответ 4

Похоже, что Reshaprer полагает, что метод Contains может изменить значение rect. Поскольку rect является readonly struct, компилятор С# делает защитные копии значения, чтобы предотвратить изменение метода в поле readonly. По сути, последний код выглядит следующим образом:

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

Resharper предупреждает вас, что Contains может мутировать rect таким образом, который был бы немедленно потерян, потому что это произошло во временное.

Ответ 5

Нечистый метод - это метод, который может иметь побочные эффекты. В этом случае Resharper кажется, что он может изменить rect. Вероятно, это не так, но цепь доказательств нарушена.