Método impuro é chamado para campo somente leitura

83

Estou usando o Visual Studio 2010 + Resharper e ele mostra um aviso no seguinte código:

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

recté um readonly Rectanglecampo, e Resharper me mostra este aviso:

"Método impuro é chamado para campo somente leitura do tipo de valor."

O que são métodos impuros e por que este aviso está sendo mostrado para mim?

Ácido
fonte

Respostas:

96

Em primeiro lugar, as respostas de Jon, Michael e Jared estão essencialmente corretas, mas tenho mais algumas coisas que gostaria de acrescentar a elas.

O que se entende por método "impuro"?

É mais fácil caracterizar métodos puros. Um método "puro" tem as seguintes características:

  • Sua saída é inteiramente determinada por sua entrada; sua saída não depende de externalidades como a hora do dia ou os bits em seu disco rígido. Sua saída não depende de seu histórico; chamar o método com um determinado argumento duas vezes deve dar o mesmo resultado.
  • Um método puro não produz mutações observáveis ​​no mundo ao seu redor. Um método puro pode escolher transformar o estado privado por uma questão de eficiência, mas um método puro não muda, digamos, um campo de seu argumento.

Por exemplo, Math.Cosé um método puro. Sua saída depende apenas de sua entrada, e a entrada não é alterada pela chamada.

Um método impuro é um método que não é puro.

Quais são alguns dos perigos de passar structs somente leitura para métodos impuros?

Existem dois que vêm à mente. O primeiro é aquele apontado por Jon, Michael e Jared, e é sobre este que Resharper está avisando. Quando você chama um método em uma estrutura, sempre passamos uma referência para a variável que é o receptor, caso o método deseje transformar a variável.

E daí se você chamar esse método em um valor, em vez de em uma variável? Nesse caso, criamos uma variável temporária, copiamos o valor para ela e passamos uma referência à variável.

Uma variável somente leitura é considerada um valor, porque não pode sofrer mutação fora do construtor. Portanto, estamos copiando a variável para outra variável, e o método impuro possivelmente está alterando a cópia, quando você pretende que ele altere a variável.

Esse é o perigo de passar uma estrutura somente leitura como um receptor . Também existe o perigo de passar uma estrutura que contém um campo somente leitura. Uma estrutura que contém um campo somente leitura é uma prática comum, mas é essencialmente um cheque de que o sistema de tipos não tem fundos para descontar; o "read-only-ness" de uma determinada variável é determinado pelo proprietário do armazenamento. Uma instância de um tipo de referência "possui" seu próprio armazenamento, mas uma instância de um tipo de valor não!

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

Pode-se pensar que isso this.xnão vai mudar porque x é um campo somente leitura e Badnessnão é um construtor. Mas...

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

... demonstra claramente a falsidade disso. thise se sreferem à mesma variável, e essa variável não é somente leitura!

Eric Lippert
fonte
Muito bem, mas considere este código: struct Id { private readonly int _id; public Id(int id) { _id = id; } public int ToInt() => _id; } Por que o ToInt é impuro?
boskicthebrain de
@boskicthebrain: A sua pergunta é realmente "por que Resharper considera isso impuro?" Se essa é a sua pergunta, encontre alguém que trabalhe com R # e pergunte!
Eric Lippert de
3
Resharper dará esse aviso mesmo se o método for inválido e não fizer nada exceto return. Com base nisso, estou supondo que o único critério é se o método tem ou não o [Pure]atributo.
bornfromanegg
Achei que esta afirmação "sempre passamos uma referência para a variável que é o receptor" é um pouco confusa para mim. No caso do PO, a que se refere 'a variável'? Eu diria que é o rect. Estamos dizendo que uma cópia de recté passada para o Containsmétodo?
xtu
51

Um método impuro é aquele que não tem garantia de deixar o valor como estava.

No .NET 4, você pode decorar métodos e tipos com [Pure]para declará-los puros, e R # vai notar isso. Infelizmente, você não pode aplicá-lo aos membros de outra pessoa e não pode convencer R # de que um tipo / membro é puro em um projeto .NET 3.5, tanto quanto eu sei. (Isso me incomoda o tempo todo no Tempo Noda .)

A ideia é que, se você estiver chamando um método que transforma uma variável, mas o chama em um campo somente leitura, ele provavelmente não está fazendo o que você deseja, então R # irá avisá-lo sobre isso. Por exemplo:

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

Este seria um aviso muito útil se todos os métodos realmente puros fossem declarados dessa forma. Infelizmente não são, portanto, há muitos falsos positivos :(

Jon Skeet
fonte
Então isso significa que um método impuro pode alterar os campos subjacentes do tipo de valor mutável transmitido a ele?
Ácido
@Acidic: Não o valor do argumento - até mesmo um método impuro pode fazer isso - mas o valor que você chama . (Veja meu exemplo, onde o método nem mesmo tem parâmetros.)
Jon Skeet
2
Você pode usar em JetBrains.Annotations.PureAttributevez de System.Diagnostics.Contracts.PureAttribute, eles têm o mesmo significado para a análise de código ReSharper e devem funcionar igualmente no .NET 3.5, .NET 4 ou Silverlight. Você também pode anotar externamente assemblies que você não possui usando arquivos XML (dê uma olhada no diretório ExternalAnnotations no caminho do bin ReSharper), pode realmente ser muito útil!
Julien Lebosquain
5
@JulienLebosquain: Eu ficaria muito relutante em começar a adicionar anotações específicas da ferramenta - especialmente para um projeto de código aberto. É bom saber sobre como opções, mas ...
Jon Skeet
1
Na verdade, descobri que System.Diagnostics.Contracts.PureAttributeesse aviso não suprimia em R # 8.2, enquanto o JetBrains.Annotations.PureAttributefazia. Os dois atributos também têm descrições diferentes: o Pureatributo de contratos implica "o resultado depende apenas de parâmetros", enquanto o JetBrains Pureimplica "não causa mudanças de estado visível" sem excluir o estado do objeto que está sendo usado para calcular o resultado. (Mas ainda assim os contratos que Purenão têm o mesmo efeito sobre este aviso provavelmente são um bug.)
Wormbo
15

A resposta curta é que esse é um falso positivo e você pode ignorar o aviso com segurança.

A resposta mais longa é que acessar um tipo de valor somente leitura cria uma cópia dele, de forma que quaisquer alterações no valor feitas por um método afetariam apenas a cópia. O ReSharper não percebe que Containsé um método puro (o que significa que não tem efeitos colaterais). Eric Lippert fala sobre isso aqui: Mutating Readonly Structs

Michael Liu
fonte
2
Nunca ignore este aviso até que seja totalmente compreendido !!! Um bom exemplo de onde isso pode protegê-lo é esta construção: private readonly SpinLock _spinLock = new SpinLock();- tal bloqueio seria completamente inútil (uma vez que o modificador somente leitura faz com que uma cópia instantânea seja criada cada vez que o método Enter é chamado)
janeiro
11

Parece que Reshaprer acredita que o método Containspode alterar o rectvalor. Por rectser um, readonly structo compilador C # faz cópias defensivas do valor para evitar que o método modifique um readonlycampo. Essencialmente, o código final se parece com isso

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

Resharper está avisando você aqui que Containspode sofrer mutação rectde uma forma que seria imediatamente perdida porque aconteceu temporariamente.

JaredPar
fonte
Então isso não impactaria nenhuma lógica executada no método, apenas evitaria que ele alterasse o valor que foi chamado, certo?
Acidic
5

Um método impuro é um método que pode ter efeitos colaterais. Nesse caso, Resharper parece pensar que isso pode mudar rect. Provavelmente não, mas a cadeia de evidências está quebrada.

Henk Holterman
fonte