2010-11-21 12 views
39

Ich versuche, eine Funktion zu erstellen, die eine Aktion erstellen kann, die jede Integerzahl inkrementiert. Jedoch gibt mein erster Versuch mir einen Fehler "ref oder nicht verwenden kann out Parameter innerhalb eines anonymen Methodenkörpers ".C# Kann ref oder out Parameter nicht innerhalb eines anonymen Methodenkörpers verwenden

public static class IntEx { 
    public static Action CreateIncrementer(ref int reference) { 
     return() => { 
      reference += 1; 
     }; 
    } 
} 

Ich verstehe, warum die Compiler dies nicht mögen, aber dennoch würde ich gerne eine anmutige Art und Weise haben, ein schönes Inkrementierer Werk zu schaffen, das auf jede ganze Zahl verweisen. Der einzige Weg, ich sehe dies zu tun, ist so etwas wie die folgende:

public static class IntEx { 
    public static Action CreateIncrementer(Func<int> getter, Action<int> setter) { 
     return() => setter(getter() + 1); 
    } 
} 

Aber natürlich, die mehr Schmerz für den Anrufer zu bedienen; Der Aufrufer muss zwei Lambdas erstellen, anstatt nur eine Referenz zu übergeben. Gibt es eine elegantere Möglichkeit, diese Funktionalität bereitzustellen, oder muss ich nur mit der Zwei-Lambda-Option leben?

+2

Ist dies ein vereinfachtes Beispiel? Warum nicht x ++ benutzen? Warum ist ein weiterer Klassenerhöhungszustand dieser Klasse – Gishu

+2

@Gishu Ja, dies ist ein vereinfachtes Beispiel; Der größere Anwendungsfall ist schwieriger zu erklären, aber es kommt darauf an, eine Aktionsfactory zu erstellen, die Operationen an Werttypen ausführen kann. –

Antwort

24

Dies ist nicht möglich.

Der Compiler wandelt alle lokalen Variablen und Parameter, die von anonymen Methoden verwendet werden, in Felder in einer automatisch generierten Closure-Klasse um.

Die CLR erlaubt keine ref Typen in Feldern zu speichern.

Wenn Sie beispielsweise einen Werttyp in einer lokalen Variablen als ref-Parameter übergeben, würde die Lebensdauer des Werts über seinen Stack-Frame hinausgehen.

30

Okay, ich habe festgestellt, dass es tatsächlich ist möglich mit Zeigern, wenn in unsicherem Kontext:

public static class IntEx { 
    unsafe public static Action CreateIncrementer(int* reference) { 
     return() => { 
      *reference += 1; 
     }; 
    } 
} 

jedoch die Garbage Collector Chaos mit dieser, indem Ihrer Referenz während der Garbage Collection anrichten kann, als das folgende zeigt an:

Man kann dieses Problem umgehen, indem man die Variable an eine bestimmte Stelle im Speicher festlegt.

public Program() { 
     GCHandle.Alloc(_i, GCHandleType.Pinned); 
    } 

, dass der Garbage Collector hält bewegt das Objekt um, so genau das, was wir suchen: Dies kann durch Hinzufügen der folgenden an den Konstruktor erfolgen. Dann müssen Sie jedoch einen Destruktor hinzufügen, um den Pin freizugeben, und er zerstört den Speicher während der gesamten Lebensdauer des Objekts. Nicht wirklich einfacher. Dies wäre in C++ sinnvoller, wo sich Dinge nicht bewegen, und Ressourcenverwaltung ist selbstverständlich, aber nicht so sehr in C#, wo alles automatisch sein soll.

So sieht aus wie die Moral der Geschichte ist, wickeln Sie dieses Mitglied int in einem Referenztyp und damit fertig sein.

(Ja, das ist die Art, wie ich es funktionierte, bevor ich die Frage stellte, aber ich versuchte nur herauszufinden, ob es einen Weg gab, alle meine Variablen loszuwerden und nur normale Ints zu benutzen Nun gut.)

+4

+1 für eine interessante Problemumgehung des verwalteten Modus mit unsicherem Kontext. –

+0

Keine Notwendigkeit für GCHandle.Alloc - erweitern Sie einfach die geschweiften Klammern der festen Aussage. –

+0

@ Mr.TA Das war nur ein Beispiel, um zu zeigen, dass der GC Dinge durcheinander bringen könnte. Jede sinnvolle Verwendung der 'incr'-Funktion würde sie wahrscheinlich von der Methode, die sie erzeugt, zurückgeben oder sie zu einem persistenten Objekt als eine Mitgliedsvariable hinzufügen, was offensichtlich den" festen "Gültigkeitsbereich verlässt. –

2

Es könnte ein nützliches Feature für die Laufzeit sein, die Erstellung von Variablenreferenzen mit einem Mechanismus zu ermöglichen, der deren Persistenz verhindert; Ein solches Merkmal hätte es einem Indexer ermöglicht, sich wie ein Array zu verhalten (z. B. könnte man auf ein Dictionary < Int32, Point> über "myDictionary [5] .X = 9;" zugreifen).Ich denke, ein solches Merkmal hätte sicher bereitgestellt werden können, wenn solche Verweise nicht auf andere Objekttypen reduziert, nicht als Felder verwendet und nicht selbst durch Referenz weitergegeben werden könnten (da jeder Ort, an dem eine solche Referenz gespeichert werden könnte, vor der Referenz außer Betracht bleiben würde selbst würde). Leider bietet die CLR keine solche Funktion.

implementieren, was Sie nach, dass der Anrufer einer Funktion, die einen Referenzparameter innerhalb eines Verschlusses verwendet erfordern würde innerhalb eines Verschlusses wickeln muss jede Variable es will eine solche Funktion zu übergeben. Wenn es eine spezielle Deklaration geben würde, um anzuzeigen, dass ein Parameter in einer solchen Weise verwendet würde, könnte es für einen Compiler praktisch sein, das erforderliche Verhalten zu implementieren. Vielleicht in einem .net 5.0 Compiler, obwohl ich nicht sicher bin, wie nützlich das wäre.

BTW, mein Verständnis ist, dass Closures in Java By-Value-Semantik verwenden, während die in .net By-Reference sind. Ich kann einige gelegentliche Verwendungen für by-reference-Semantik verstehen, aber die Verwendung der Referenz als Standard scheint eine zweifelhafte Entscheidung zu sein, analog zur Verwendung der Standard-By-Reference-Parameterübergabe-Semantik für VB-Versionen bis VB6. Wenn Sie den Wert einer Variablen beim Erstellen eines Delegaten zum Aufrufen einer Funktion erfassen möchten (z. B. wenn ein Delegat MyFunction (X) mit dem Wert von X aufrufen soll, wenn der Delegat erstellt wird), ist es besser, ein Lambda zu verwenden mit einer zusätzlichen Temperatur, oder ist es besser, einfach eine Delegiertenfabrik zu verwenden und sich nicht mit Lambda-Ausdrücken zu beschäftigen.

+1

Ja, Eric Lippert hat darüber einen Blog geschrieben. http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-consided-harmful.aspx. Ich habe tatsächlich Code, der von diesem Verhalten abhängt; Beibehalten einer Framezahl für die OpenGL-Schleife, ohne dass beispielsweise eine Framecount-Elementvariable benötigt wird. Ich mag die C++ - Spezifikation besser, die entweder semantisch erlaubt. –

+1

@Dax: Es gibt Fälle, in denen Pass-by-Reference-Semantik notwendig ist. Das Gleiche gilt jedoch für die Parameterübergabe. Dies bedeutet nicht, dass Voreinstellung der Standardwert sein sollte. BTW, ich frage mich, was die Vor- und Nachteile gewesen wären, Referenz-Closure-Variablen durch Single-Element-Arrays zu ersetzen, wodurch verschiedene Klassen für anonyme Methoden erstellt werden können, die verschiedene Kombinationen von Closure-Variablen benötigen. – supercat