Die unreine Methode wird für das schreibgeschützte Feld aufgerufen

83

Ich verwende Visual Studio 2010 + Resharper und es wird eine Warnung zum folgenden Code angezeigt :

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

rectist ein readonly RectangleFeld und Resharper zeigt mir diese Warnung:

"Die unreine Methode wird für ein schreibgeschütztes Feld vom Werttyp aufgerufen."

Was sind unreine Methoden und warum wird mir diese Warnung angezeigt?

Säure
quelle

Antworten:

96

Zunächst einmal sind die Antworten von Jon, Michael und Jared im Wesentlichen richtig, aber ich möchte noch ein paar Dinge hinzufügen.

Was ist mit einer "unreinen" Methode gemeint?

Es ist einfacher, reine Methoden zu charakterisieren. Eine "reine" Methode hat folgende Eigenschaften:

  • Seine Ausgabe wird vollständig durch seine Eingabe bestimmt; Die Ausgabe hängt nicht von externen Faktoren wie der Tageszeit oder den Bits auf Ihrer Festplatte ab. Seine Ausgabe hängt nicht von seiner Geschichte ab; Das zweimalige Aufrufen der Methode mit einem bestimmten Argument sollte das gleiche Ergebnis liefern.
  • Eine reine Methode erzeugt keine beobachtbaren Mutationen in der Welt um sie herum. Eine reine Methode kann sich aus Effizienzgründen dafür entscheiden, den privaten Staat zu mutieren, aber eine reine Methode mutiert beispielsweise nicht ein Feld ihrer Argumentation.

Zum Beispiel Math.Cosist eine reine Methode. Die Ausgabe hängt nur von der Eingabe ab, und die Eingabe wird durch den Aufruf nicht geändert.

Eine unreine Methode ist eine Methode, die nicht rein ist.

Was sind einige der Gefahren, wenn schreibgeschützte Strukturen an unreine Methoden übergeben werden?

Es gibt zwei, die mir in den Sinn kommen. Das erste ist das, auf das Jon, Michael und Jared hingewiesen haben, und das ist das, vor dem Resharper Sie warnt. Wenn Sie eine Methode für eine Struktur aufrufen, übergeben wir immer einen Verweis auf die Variable, die der Empfänger ist, falls die Methode die Variable mutieren möchte.

Was ist, wenn Sie eine solche Methode für einen Wert und nicht für eine Variable aufrufen? In diesem Fall erstellen wir eine temporäre Variable, kopieren den Wert hinein und übergeben einen Verweis auf die Variable.

Eine schreibgeschützte Variable wird als Wert betrachtet, da sie außerhalb des Konstruktors nicht mutiert werden kann. Wir kopieren die Variable also in eine andere Variable, und die unreine Methode mutiert möglicherweise die Kopie, wenn Sie beabsichtigen, die Variable zu mutieren.

Das ist die Gefahr, eine schreibgeschützte Struktur als Empfänger zu übergeben . Es besteht auch die Gefahr, dass eine Struktur übergeben wird, die ein schreibgeschütztes Feld enthält. Eine Struktur, die ein schreibgeschütztes Feld enthält, ist eine gängige Praxis, aber es wird im Wesentlichen ein Scheck ausgestellt, dass das Typsystem nicht über die Mittel verfügt, um Bargeld zu erhalten. Die "Nur-Lese-Fähigkeit" einer bestimmten Variablen wird vom Eigentümer des Speichers bestimmt. Eine Instanz eines Referenztyps "besitzt" ihren eigenen Speicher, eine Instanz eines Werttyps jedoch nicht!

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

Man glaubt, dass this.xsich das nicht ändern wird, weil x ein schreibgeschütztes Feld und Badnesskein Konstruktor ist. Aber...

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

... zeigt deutlich die Falschheit davon. thisund sverweisen auf dieselbe Variable, und diese Variable ist nicht schreibgeschützt!

Eric Lippert
quelle
Fair genug, aber bitte beachten Sie diesen Code: struct Id { private readonly int _id; public Id(int id) { _id = id; } public int ToInt() => _id; } Warum ist ToInt unrein?
Boskicthebrain
@ Boskicthebrain: Ist Ihre Frage tatsächlich "Warum hält Resharper dies für unrein?" Wenn das deine Frage ist, dann finde jemanden, der an R # arbeitet und frage ihn!
Eric Lippert
3
Resharper gibt diese Warnung auch dann aus, wenn die Methode ungültig ist und nichts anderes tut als return. Auf dieser Grundlage schätze ich, dass das einzige Kriterium darin besteht, ob die Methode das [Pure]Attribut hat oder nicht .
geboren
Ich fand diese Aussage "Wir übergeben immer einen Verweis auf die Variable, die der Empfänger ist" etwas verwirrend für mich. Worauf bezieht sich im Fall der Bestellung die Variable? Ich würde annehmen, dass es das ist rect. Wollen wir damit sagen, dass eine Kopie von rectan Containsmethod übergeben wird?
xtu
51

Eine unreine Methode ist eine, bei der nicht garantiert wird, dass der Wert unverändert bleibt.

In .NET 4 können Sie Methoden und Typen mit dekorieren [Pure], um sie als rein zu deklarieren, und R # wird dies zur Kenntnis nehmen. Leider können Sie es nicht auf andere Mitglieder anwenden und R # nicht davon überzeugen, dass ein Typ / Mitglied meines Wissens in einem .NET 3.5-Projekt rein ist. (Das beißt mich die ganze Zeit in der Noda- Zeit.)

Die Idee ist, dass wenn Sie eine Methode aufrufen, die eine Variable mutiert, diese aber in einem schreibgeschützten Feld aufruft, sie wahrscheinlich nicht das tut, was Sie wollen, sodass R # Sie davor warnt. Beispielsweise:

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

Dies wäre eine wirklich nützliche Warnung, wenn jede Methode, die tatsächlich rein war, auf diese Weise deklariert würde. Leider nicht, daher gibt es viele Fehlalarme :(

Jon Skeet
quelle
Das bedeutet also, dass eine unreine Methode die zugrunde liegenden Felder des an sie übergebenen veränderlichen Werttyps ändern könnte?
Acidic
@Acidic: Nicht der Argumentwert - selbst eine unreine Methode kann das -, sondern der Wert, auf den Sie ihn aufrufen . (Siehe mein Beispiel, wo die Methode nicht einmal Parameter hat.)
Jon Skeet
2
Sie können JetBrains.Annotations.PureAttributeanstelle von verwenden System.Diagnostics.Contracts.PureAttribute, sie haben dieselbe Bedeutung für die ReSharper-Code-Analyse und sollten unter .NET 3.5, .NET 4 oder Silverlight gleichermaßen funktionieren. Sie können Assemblys, die Sie nicht besitzen, auch extern mit XML-Dateien versehen (sehen Sie sich das Verzeichnis ExternalAnnotations im ReSharper-Bin-Pfad an). Dies kann sehr nützlich sein!
Julien Lebosquain
5
@ JulienLebosquain: Ich würde wirklich zögern, werkzeugspezifische Anmerkungen hinzuzufügen - insbesondere für ein Open Source-Projekt. Gut zu wissen als Option, aber ...
Jon Skeet
1
Eigentlich habe ich festgestellt, dass System.Diagnostics.Contracts.PureAttributediese Warnung in R # 8.2 nicht unterdrückt wurde JetBrains.Annotations.PureAttribute. Die beiden Attribute haben auch unterschiedliche Beschreibungen: Das Vertragsattribut Pureimpliziert "Ergebnis hängt nur von Parametern ab", während JetBrains Pureimpliziert "keine sichtbaren Statusänderungen verursacht", ohne den Objektstatus auszuschließen, der zur Berechnung des Ergebnisses verwendet wird. (Aber immer noch Verträge, die Purenicht die gleiche Wirkung auf diese Warnung haben, sind wahrscheinlich ein Fehler.)
Wormbo
15

Die kurze Antwort lautet, dass dies ein falsches Positiv ist und Sie die Warnung ignorieren können.

Die längere Antwort lautet, dass beim Zugriff auf einen schreibgeschützten Werttyp eine Kopie davon erstellt wird, sodass Änderungen am Wert, die von einer Methode vorgenommen werden, nur die Kopie betreffen. ReSharper erkennt nicht, dass dies Containseine reine Methode ist (was bedeutet, dass es keine Nebenwirkungen hat). Eric Lippert spricht hier darüber: Mutating Readonly Structs

Michael Liu
quelle
2
Bitte ignorieren Sie diese Warnung niemals, bis Sie sie vollständig verstanden haben !!! Ein gutes Beispiel, wo dies Sie schützen kann, ist dieses Konstrukt: private readonly SpinLock _spinLock = new SpinLock();- Eine solche Sperre wäre völlig nutzlos (da der schreibgeschützte Modifikator jedes Mal, wenn eine Enter-Methode aufgerufen wird, eine On-the-Fly-Kopie erstellt)
Januar
11

Es klingt so, als ob Reshaprer glaubt, dass die Methode Containsden rectWert mutieren kann. Da recteine ist readonly structder Compiler C # macht defensive Kopien des Wertes des Verfahrens von Mutieren eines verhindern readonlyFeld. Im Wesentlichen sieht der endgültige Code so aus

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

Resharper warnt Sie hier, dass Containses zu recteiner Mutation kommen kann, die sofort verloren geht, weil es vorübergehend passiert ist.

JaredPar
quelle
Das würde also keine Logik beeinflussen, die in der Methode ausgeführt wird, sondern nur verhindern, dass sie den Wert mutiert, den sie aufgerufen hat, oder?
Acidic
5

Eine unreine Methode ist eine Methode, die Nebenwirkungen haben kann. In diesem Fall scheint Resharper zu glauben, dass sich dies ändern könnte rect. Wahrscheinlich nicht, aber die Beweiskette ist unterbrochen.

Henk Holterman
quelle