El método impuro se llama para campo de solo lectura

83

Estoy usando Visual Studio 2010 + Resharper y muestra una advertencia en el siguiente código:

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

rectes un readonly Rectanglecampo, y Resharper me muestra esta advertencia:

"Impure Method se llama para campos de solo lectura de tipo de valor".

¿Qué son los métodos impuros y por qué se me muestra esta advertencia?

Ácido
fuente

Respuestas:

96

En primer lugar, las respuestas de Jon, Michael y Jared son esencialmente correctas, pero tengo algunas cosas más que me gustaría agregarles.

¿Qué se entiende por método "impuro"?

Es más fácil caracterizar métodos puros. Un método "puro" tiene las siguientes características:

  • Su salida está completamente determinada por su entrada; su salida no depende de factores externos como la hora del día o los bits de su disco duro. Su salida no depende de su historia; llamar al método con un argumento dado dos veces debería dar el mismo resultado.
  • Un método puro no produce mutaciones observables en el mundo que lo rodea. Un método puro puede optar por mutar un estado privado en aras de la eficiencia, pero un método puro no, por ejemplo, muta un campo de su argumento.

Por ejemplo, Math.Coses un método puro. Su salida depende solo de su entrada y la entrada no se modifica con la llamada.

Un método impuro es un método que no es puro.

¿Cuáles son algunos de los peligros de pasar estructuras de solo lectura a métodos impuros?

Hay dos que me vienen a la mente. El primero es el señalado por Jon, Michael y Jared, y este es el que te advierte Resharper. Cuando llama a un método en una estructura, siempre pasamos una referencia a la variable que es el receptor, en caso de que el método desee mutar la variable.

Entonces, ¿qué pasa si llama a un método de este tipo en un valor, en lugar de una variable? En ese caso, creamos una variable temporal, copiamos el valor en ella y pasamos una referencia a la variable.

Una variable de solo lectura se considera un valor porque no se puede mutar fuera del constructor. Entonces, estamos copiando la variable a otra variable, y el método impuro posiblemente está mutando la copia, cuando usted tiene la intención de que mute la variable.

Ese es el peligro de pasar una estructura de solo lectura como receptor . También existe el peligro de pasar una estructura que contiene un campo de solo lectura. Una estructura que contiene un campo de solo lectura es una práctica común, pero esencialmente se trata de emitir un cheque que el sistema de tipos no tiene los fondos para cobrar; la "condición de sólo lectura" de una variable en particular la determina el propietario del almacenamiento. Una instancia de un tipo de referencia "posee" su propio almacenamiento, ¡pero una instancia de un tipo de valor no!

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

Uno piensa que eso this.xno va a cambiar porque x es un campo de solo lectura y Badnessno es un constructor. Pero...

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

... demuestra claramente la falsedad de eso. thisy hacen sreferencia a la misma variable, ¡y esa variable no es de solo lectura!

Eric Lippert
fuente
Bastante razonable, pero tenga en cuenta este código: struct Id { private readonly int _id; public Id(int id) { _id = id; } public int ToInt() => _id; } ¿Por qué ToInt es impuro?
boskicthebrain
@boskicthebrain: ¿Su pregunta es en realidad "por qué Resharper considera que esto es impuro?" Si esa es tu pregunta, busca a alguien que trabaje en R # y pregúntale.
Eric Lippert
3
Resharper dará esta advertencia incluso si el método es nulo y no hace nada excepto return. Basado en eso, supongo que el único criterio es si el método tiene el [Pure]atributo o no .
bornfromanegg
Encontré que esta declaración "siempre pasamos una referencia a la variable que es el receptor" es un poco confusa para mí. En el caso del PO, ¿a qué se refiere 'la variable'? Asumiría que es el rect. ¿Estamos diciendo que rectse pasa una copia de Containsmétodo?
xtu
51

Un método impuro es aquel que no garantiza que deje el valor como estaba.

En .NET 4 puede decorar métodos y tipos con [Pure]para declararlos puros, y R # se dará cuenta de esto. Desafortunadamente, no puede aplicarlo a los miembros de otra persona, y no puede convencer a R # de que un tipo / miembro es puro en un proyecto .NET 3.5 hasta donde yo sé. (Esto me muerde en Noda Time todo el tiempo).

La idea es que si está llamando a un método que muta una variable, pero lo llama en un campo de solo lectura, probablemente no esté haciendo lo que desea, por lo que R # le advertirá sobre esto. Por ejemplo:

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

Esta sería una advertencia realmente útil si todos los métodos que en realidad fueran puros fueran declarados de esa manera. Lamentablemente no lo son, por lo que hay muchos falsos positivos :(

Jon Skeet
fuente
Entonces, ¿eso significa que un método impuro podría cambiar los campos subyacentes del tipo de valor mutable que se le pasa?
Acidic
@Acidic: No el valor del argumento, incluso un método impuro puede hacer eso, sino el valor al que lo llama . (Vea mi ejemplo, donde el método ni siquiera tiene ningún parámetro.)
Jon Skeet
2
Puede usar en JetBrains.Annotations.PureAttributelugar de System.Diagnostics.Contracts.PureAttribute, tienen el mismo significado para el análisis de código de ReSharper y deberían funcionar igualmente en .NET 3.5, .NET 4 o Silverlight. También puede anotar externamente ensamblajes que no posee usando archivos XML (eche un vistazo al directorio ExternalAnnotations en la ruta bin de ReSharper), ¡realmente puede ser bastante útil!
Julien Lebosquain
5
@JulienLebosquain: Sería realmente reacio a comenzar a agregar anotaciones específicas de herramientas, especialmente para un proyecto de código abierto. Es bueno saberlo como una opción, pero ...
Jon Skeet
1
En realidad, encontré que System.Diagnostics.Contracts.PureAttributeno suprimió esta advertencia en R # 8.2, mientras que lo JetBrains.Annotations.PureAttributehizo. Los dos atributos también tienen descripciones diferentes: el Pureatributo contratos implica "el resultado depende sólo de los parámetros", mientras que JetBrains Pureimplica "no causa cambios de estado visibles" sin excluir el estado del objeto que se utiliza para calcular el resultado. (Pero aún así los contratos que Pureno tienen el mismo efecto en esta advertencia probablemente sea un error).
Wormbo
15

La respuesta corta es que se trata de un falso positivo y puede ignorar la advertencia con seguridad.

La respuesta más larga es que acceder a un tipo de valor de solo lectura crea una copia del mismo, por lo que cualquier cambio en el valor realizado por un método solo afectaría la copia. ReSharper no se da cuenta de que Containses un método puro (lo que significa que no tiene efectos secundarios). Eric Lippert habla de ello aquí: Mutación de estructuras de solo lectura

Michael Liu
fuente
2
¡Nunca ignore esta advertencia hasta que la comprenda completamente! Un buen ejemplo donde esto puede segura que esta construcción es: private readonly SpinLock _spinLock = new SpinLock();- un bloqueo de este tipo sería completamente inútil (ya que sólo lectura causas modificadoras de la marcha de la copia que se crea cada vez que se llama método Introduzca en él)
Ene
11

Parece que Reshaprer cree que el método Containspuede mutar el rectvalor. Debido a que rectes un, readonly structel compilador de C # hace copias defensivas del valor para evitar que el método mute un readonlycampo. Básicamente, el código final se ve así

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

Resharper le advierte aquí que Containspuede mutar rectde una manera que se perdería inmediatamente porque sucedió de forma temporal.

JaredPar
fuente
Entonces eso no afectaría ninguna lógica realizada en el método, solo evitaría que mute el valor que se le solicitó, ¿verdad?
Acidic
5

Un método impuro es un método que puede tener efectos secundarios. En este caso, Resharper parece pensar que podría cambiar rect. Probablemente no sea así, pero la cadena de pruebas está rota.

Henk Holterman
fuente