Ich habe die folgende Erweiterungsmethode:
public static IEnumerable<T> Apply<T>(
[NotNull] this IEnumerable<T> source,
[NotNull] Action<T> action)
where T : class
{
source.CheckArgumentNull("source");
action.CheckArgumentNull("action");
return source.ApplyIterator(action);
}
private static IEnumerable<T> ApplyIterator<T>(this IEnumerable<T> source, Action<T> action)
where T : class
{
foreach (var item in source)
{
action(item);
yield return item;
}
}
Es wird lediglich eine Aktion auf jedes Element der Sequenz angewendet, bevor es zurückgegeben wird.
Ich habe mich gefragt, ob ich das Pure
Attribut (aus Resharper-Annotationen) auf diese Methode anwenden soll , und ich kann Argumente dafür und dagegen sehen.
Vorteile:
- streng genommen, es ist rein; Wenn Sie es nur für eine Sequenz aufrufen, ändert sich weder die Sequenz (es wird eine neue Sequenz zurückgegeben) noch wird eine beobachtbare Statusänderung vorgenommen
- Das Aufrufen ohne Verwendung des Ergebnisses ist eindeutig ein Fehler, da es keine Auswirkung hat, wenn die Sequenz nicht aufgezählt wird. Daher möchte ich, dass Resharper mich warnt, wenn ich das tue.
Nachteile:
- obwohl die
Apply
Methode selbst rein ist, die resultierende Sequenz Aufzählen wird machen beobachtbaren Zustandsänderungen (das ist der Punkt des Verfahrens ist). Ändert beispielsweiseitems.Apply(i => i.Count++)
die Werte der Elemente jedes Mal, wenn sie aufgelistet werden. Das Anwenden des Pure-Attributs ist also wahrscheinlich irreführend ...
Was denkst du? Soll ich das Attribut anwenden oder nicht?
c#
pure-function
Thomas Levesque
quelle
quelle
Antworten:
Nein, es ist nicht rein, weil es Nebenwirkungen hat. Konkret ruft es
action
jeden Gegenstand auf. Es ist auch nicht threadsicher.Die Haupteigenschaft reiner Funktionen ist, dass sie beliebig oft aufgerufen werden können und niemals etwas anderes tun, als denselben Wert zurückzugeben. Welches ist nicht dein Fall. Rein zu sein bedeutet auch, dass Sie nichts anderes als die Eingabeparameter verwenden. Dies bedeutet, dass es jederzeit von jedem Thread aus aufgerufen werden kann und kein unerwartetes Verhalten verursacht. Auch dies ist bei Ihrer Funktion nicht der Fall.
Sie könnten sich auch in einer Sache irren: Funktionsreinheit ist keine Frage der Vor- oder Nachteile. Selbst ein einziger Zweifel, dass es Nebenwirkungen haben kann, reicht aus, um es nicht rein zu machen.
Eric Lippert spricht einen guten Punkt an. Ich werde http://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx als Teil meines Gegenarguments verwenden. Besonders Linie
Nehmen wir an, wir erstellen eine Methode wie diese:
Erstens setzt dies voraus, dass dies auch
GetEnumerator
rein ist (ich kann keine Quelle dafür finden). Wenn dies der Fall ist, können wir diese Methode gemäß der obigen Regel mit [Pure] kommentieren, da nur die Instanz geändert wird, die im Hauptteil selbst erstellt wurde. Danach können wir dies und das komponierenApplyIterator
, was zu einer reinen Funktion führen sollte, oder?Nein. Diese Zusammensetzung ist nicht rein, auch wenn beide
Count
undApplyIterator
rein sind. Aber ich könnte dieses Argument auf einer falschen Prämisse aufbauen. Ich denke, dass die Idee, dass Instanzen, die innerhalb der Methode erstellt wurden, von der Reinheitsregel ausgenommen sind, entweder falsch oder zumindest nicht spezifisch genug ist.quelle
where T : class
würde das OPwhere T : strut
jedoch rein sein , wenn das OP es einfach formulierte .sequence.Apply(action)
hat keine Nebenwirkung; Wenn dies der Fall ist, geben Sie die Nebenwirkung an, die es hat. Jetzt hat das Anrufensequence.Apply(action).GetEnumerator().MoveNext()
einen Nebeneffekt, aber das wussten wir bereits. es mutiert den Enumerator! Warum solltesequence.Apply(action)
man es als unrein betrachten, weil das RufenMoveNext
unrein ist, abersequence.Where(predicate)
als rein angesehen werden?sequence.Where(predicate).GetEnumerator().MoveNext()
ist genauso unrein.GetEnumerator
, abgesehen von der Zuweisung eines Enumerators im Ausgangszustand?Ich bin mit den Antworten von Euphoric und Robert Harvey nicht einverstanden . Absolut das ist eine reine Funktion; Das Problem ist, dass
ist sehr unklar, was das erste "es" bedeutet. Wenn "es" eine dieser Funktionen bedeutet, dann ist das nicht richtig; Keine dieser Funktionen macht das; Der
MoveNext
Enumerator der Sequenz tut dies und "gibt" das Element über dieCurrent
Eigenschaft zurück, nicht durch Rückgabe.Diese Sequenzen werden träge und nicht eifrig aufgezählt , so dass es sicherlich nicht der Fall ist, dass die Aktion angewendet wird, bevor die Sequenz von zurückgegeben wird
Apply
. Die Aktion wird angewendet, nachdem die Sequenz zurückgegeben wurde, wennMoveNext
sie auf einem Enumerator aufgerufen wird.Wie Sie bemerken, führen diese Funktionen eine Aktion und eine Sequenz aus und geben eine Sequenz zurück. Die Ausgabe hängt von der Eingabe ab und es werden keine Nebenwirkungen erzeugt. Dies sind also reine Funktionen.
Wenn Sie nun einen Enumerator für die resultierende Sequenz erstellen und dann MoveNext für diesen Iterator aufrufen, ist die MoveNext-Methode nicht rein, da sie die Aktion aufruft und einen Nebeneffekt erzeugt. Aber wir wussten bereits, dass MoveNext nicht rein ist, weil es den Enumerator mutiert!
Was nun Ihre Frage betrifft, sollten Sie das Attribut anwenden: Ich würde das Attribut nicht anwenden, da ich diese Methode überhaupt nicht schreiben würde . Wenn ich eine Aktion auf eine Sequenz anwenden möchte, schreibe ich
das ist schön klar.
quelle
ForEach
Verlängerungsmethode, die absichtlich nicht Teil von Linq ist, weil ihr Ziel darin besteht, Nebenwirkungen zu erzeugen ...Any()
Laufe der Zeit mehreren Aufrufen unterzogen wird. Die Aktion wird immer wieder ausgeführt, jedoch nur beim ersten Gegenstand! Eine Sequenz sollte eine Folge von Werten sein . Wenn Sie eine Abfolge von Aktionen wünschen, machen Sie eineIEnumerable<Action>
.action
, daher ist die Reinheit vonaction
irrelevant. Ich weiß, es sieht so aus, als würde es aufgerufenaction
, aber diese Methode ist ein syntaktischer Zucker für zwei Methoden, eine, die einen Enumerator zurückgibt, und eine, dieMoveNext
der Enumerator ist. Ersteres ist eindeutig rein und Letzteres eindeutig nicht. Betrachten Sie es so: Würden Sie sagen, dassIEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); }
das rein ist? Denn das ist die Funktion, die das wirklich ist.ApplyIterator
Methode kehrt sofort zurück .ApplyIterator
Bis zum ersten AufrufMoveNext
des Enumerators des zurückgegebenen Objekts wird kein Code im Hauptteil von ausgeführt . Nachdem Sie das wissen, können Sie die Antwort auf dieses Rätsel ableiten: blogs.msdn.com/b/ericlippert/archive/2007/09/05/… Die Antwort lautet hier: blogs.msdn.com/b/ericlippert/archive / 2007/09/06 /…Es ist keine reine Funktion, daher ist die Anwendung des Pure-Attributs irreführend.
Reine Funktionen ändern die ursprüngliche Sammlung nicht und es spielt keine Rolle, ob Sie eine Aktion übergeben, die keine Auswirkung hat oder nicht. Es ist immer noch eine unreine Funktion, weil es Nebenwirkungen verursachen soll.
Wenn Sie die Funktion rein machen möchten, kopieren Sie die Sammlung in eine neue Sammlung, wenden Sie die von der Aktion vorgenommenen Änderungen auf die neue Sammlung an und geben Sie die neue Sammlung zurück, wobei die ursprüngliche Sammlung unverändert bleibt.
quelle
item
es sich um einen Referenztyp handelt, wird die ursprüngliche Sammlung geändert, obwohl Sieitem
in einem Iterator zurückkehren. Siehe stackoverflow.com/questions/1538301action
sie andere Nebenwirkungen haben könnte, als das an sie übergebene Objekt zu ändern.()=>{}
ist in Action konvertierbar und eine reine Funktion. Die Ergebnisse hängen ausschließlich von den Eingaben ab und es treten keine beobachtbaren Nebenwirkungen auf.Meiner Meinung nach macht die Tatsache, dass es eine Aktion erhält (und nicht so etwas wie PureAction), es nicht rein.
Und ich bin sogar anderer Meinung als Eric Lippert. Er schrieb: "() => {} ist in Action konvertierbar und eine reine Funktion. Die Ausgaben hängen ausschließlich von den Eingaben ab und es gibt keine beobachtbaren Nebenwirkungen."
Stellen Sie sich vor, der ApplyIterator hat anstelle eines Delegaten eine Methode namens Action aufgerufen.
Wenn Action rein ist, ist auch der ApplyIterator rein. Wenn Action nicht rein ist, kann der ApplyIterator nicht rein sein.
In Anbetracht des Delegatentyps (nicht des tatsächlich angegebenen Werts) können wir nicht garantieren, dass er rein ist. Daher verhält sich die Methode nur dann als reine Methode, wenn der Delegat rein ist. Um es wirklich rein zu machen, sollte es einen reinen Delegaten erhalten (und das existiert, wir können einen Delegaten als [Pure] deklarieren, damit wir eine PureAction haben können).
Anders ausgedrückt sollte eine Pure-Methode bei gleichen Eingaben immer das gleiche Ergebnis liefern und keine beobachtbaren Änderungen erzeugen. ApplyIterator kann dieselbe Quelle und denselben Delegaten zweimal erhalten. Wenn der Delegat jedoch einen Referenztyp ändert, führt die nächste Ausführung zu unterschiedlichen Ergebnissen. Beispiel: Der Delegat macht so etwas wie item.Content + = "Changed";
Wenn Sie also den ApplyIterator über eine Liste von "Zeichenfolgencontainern" (ein Objekt mit einer Content-Eigenschaft vom Typ Zeichenfolge) verwenden, haben wir möglicherweise die folgenden ursprünglichen Werte:
Nach der ersten Ausführung enthält die Liste Folgendes:
Und das zum 3. Mal:
Daher ändern wir den Inhalt der Liste, da der Delegat nicht rein ist und keine Optimierung durchgeführt werden kann, um zu vermeiden, dass der Aufruf dreimal ausgeführt wird, wenn er dreimal aufgerufen wird, da jede Ausführung ein anderes Ergebnis generiert.
quelle