Eine Eigenschaft oder ein Indexer darf nicht als out- oder ref-Parameter übergeben werden

83

Ich erhalte den oben genannten Fehler und kann ihn nicht beheben. Ich habe ein bisschen gegoogelt, kann es aber nicht loswerden.

Szenario:

Ich habe die Klasse BudgetAllocate, deren Eigenschaft das Budget ist, das vom doppelten Typ ist.

In meinem dataAccessLayer

In einer meiner Klassen versuche ich Folgendes:

double.TryParse(objReader[i].ToString(), out bd.Budget);

Welches wirft diesen Fehler:

Eigenschaft oder Indexer dürfen zur Kompilierungszeit nicht als out- oder ref-Parameter übergeben werden.

Ich habe es sogar versucht:

double.TryParse(objReader[i].ToString().Equals(DBNull.Value) ? "" : objReader[i].ToString(), out bd.Budget);

Alles andere funktioniert einwandfrei und Referenzen zwischen den Ebenen sind vorhanden.

Pratik
quelle
In bd.Budget ist bd ein Objekt der Klasse BudgetAllocate. Entschuldigung ich vergaß.
Pratik
1
Mögliches Duplikat des Zugriffs auf Eigenschaften über den generischen Typparameter
Chris Moschini
1
Mögliches Duplikat von Passing-Eigenschaften durch Bezugnahme in C #
Legolas
Ich habe gerade festgestellt, dass dies mit einem Benutzertyp funktioniert, für den Felder definiert wurden, von denen ich erwartet hatte DataGrid, dass sie ausgefüllt werden, um dann nur Autos mit Eigenschaften zu lernen. Durch das Wechseln zu Eigenschaften wurden einige Ref-Parameter beschädigt, die ich für meine Felder verwendet habe. Muss lokale Variablen definieren, mit denen das Parsen durchgeführt werden soll.
Jxramos

Antworten:

35

Sie können nicht verwenden

double.TryParse(objReader[i].ToString(), out bd.Budget); 

Ersetzen Sie bd.Budget durch eine Variable.

double k;
double.TryParse(objReader[i].ToString(), out k); 
Dhinesh
quelle
9
Warum eine zusätzliche Variable verwenden?
Pratik
6
@pratik Sie können eine Eigenschaft nicht als out-Parameter übergeben, da nicht garantiert werden kann, dass die Eigenschaft tatsächlich einen Setter hat. Daher benötigen Sie die zusätzliche Variable.
Matt
23
@ mjd79: Deine Argumentation ist falsch. Der Compiler weiß, ob es einen Setter gibt oder nicht. Angenommen, es gab einen Setter; sollte es erlaubt sein?
Eric Lippert
21
@dhinesh, ich denke, das OP sucht nach einer Antwort, warum er es nicht kann, nicht nur was er stattdessen tun muss. Lesen Sie die Antwort von Hans Passant und die Kommentare von Eric Lippert.
Slugster
2
@dhinesh Der "wahre" Grund, warum er es nicht kann, ist, dass er C # anstelle von VB verwendet, was dies erlaubt. Ich komme aus der VB-Welt (offensichtlich?) Und bin oft überrascht über die zusätzlichen Einschränkungen, die C # auferlegt.
SteveCinq
144

Andere haben Ihnen die Lösung gegeben, aber warum dies notwendig ist: Eine Eigenschaft ist nur syntaktischer Zucker für eine Methode .

Wenn Sie beispielsweise eine Eigenschaft deklarieren, die Namemit einem Getter und einem Setter aufgerufen wird, generiert der Compiler unter der Haube tatsächlich Methoden mit dem Namen get_Name()und set_Name(value). Wenn Sie dann aus dieser Eigenschaft lesen und in sie schreiben, übersetzt der Compiler diese Operationen in Aufrufe dieser generierten Methoden.

Wenn Sie dies berücksichtigen, wird klar, warum Sie eine Eigenschaft nicht als Ausgabeparameter übergeben können - Sie würden tatsächlich einen Verweis auf eine Methode übergeben , anstatt einen Verweis auf ein Objekt, eine Variable , wie es ein Ausgabeparameter erwartet.

Ein ähnlicher Fall besteht für Indexer.

Mike Chamberlain
quelle
18
Ihre Argumentation ist bis zum letzten Bit richtig. Der out-Parameter erwartet einen Verweis auf eine Variable , nicht auf ein Objekt .
Eric Lippert
@EricLippert aber ist eine Variable nicht auch ein Objekt oder was fehlt mir?
MeJustAndrew
4
@meJustAndrew: Eine Variable ist absolut kein Objekt . Eine Variable ist ein Speicherort . Ein Speicherort enthält entweder (1) eine Referenz auf ein Objekt vom Referenztyp (oder null) oder (2) den Wert eines Objekts vom Werttyp. Verwechseln Sie den Behälter nicht mit dem darin enthaltenen Gegenstand.
Eric Lippert
5
@meJustAndrew: Betrachten Sie ein Objekt, sagen wir ein Haus. Stellen Sie sich ein Stück Papier vor, auf dem die Adresse des Hauses steht. Stellen Sie sich eine Schublade vor, die dieses Stück Papier enthält. Weder die Schublade noch das Papier sind das Haus .
Eric Lippert
66

Dies ist ein Fall einer undichten Abstraktion. Eine Eigenschaft ist eigentlich eine Methode, die get und set Accessoren für einen Indexer get_Index kompilierten get () und set_Index Methoden. Der Compiler leistet hervorragende Arbeit, indem er diese Tatsache verbirgt. Er übersetzt beispielsweise automatisch eine Zuweisung zu einer Eigenschaft in die entsprechende set_Xxx () -Methode.

Dies geht jedoch schief, wenn Sie einen Methodenparameter als Referenz übergeben. Dazu muss der JIT-Compiler einen Zeiger auf den Speicherort des übergebenen Arguments übergeben. Das Problem ist, dass es keine gibt. Um den Wert einer Eigenschaft zuzuweisen, muss die Setter-Methode aufgerufen werden. Die aufgerufene Methode kann den Unterschied zwischen einer übergebenen Variablen und einer übergebenen Eigenschaft nicht erkennen und kann daher nicht wissen, ob ein Methodenaufruf erforderlich ist.

Bemerkenswert ist, dass dies tatsächlich in VB.NET funktioniert. Beispielsweise:

Class Example
    Public Property Prop As Integer

    Public Sub Test(ByRef arg As Integer)
        arg = 42
    End Sub

    Public Sub Run()
        Test(Prop)   '' No problem
    End Sub
End Class

Der VB.NET-Compiler löst dieses Problem, indem er diesen Code für die Run-Methode automatisch generiert, ausgedrückt in C #:

int temp = Prop;
Test(ref temp);
Prop = temp;

Welches ist die Problemumgehung, die Sie auch verwenden können. Ich bin mir nicht ganz sicher, warum das C # -Team nicht denselben Ansatz gewählt hat. Möglicherweise, weil sie die potenziell teuren Getter- und Setter-Anrufe nicht verbergen wollten. Oder das völlig nicht diagnostizierbare Verhalten, das auftritt, wenn der Setter Nebenwirkungen hat, die den Eigenschaftswert ändern, verschwindet nach der Zuweisung. Klassischer Unterschied zwischen C # und VB.NET, C # ist "keine Überraschungen", VB.NET ist "dafür sorgen, dass es funktioniert, wenn Sie können".

Hans Passant
quelle
14
Sie haben Recht, wenn Sie die teuren Anrufe nicht generieren möchten. Ein sekundärer Grund ist, dass die Copant-In-Copy-Out-Semantik eine andere Semantik als die Referenzsemantik hat und es inkonsistent wäre, zwei subtil unterschiedliche Semantiken für die Ref-Übergabe zu haben. (Allerdings gibt es einige seltene Situationen, in denen kompilierte Ausdrucksbäume leider kopieren und kopieren.)
Eric Lippert
2
Was wirklich benötigt wird, ist eine größere Auswahl an Parameterübergabemodi, so dass der Compiler gegebenenfalls "Kopieren in / Kopieren" ersetzen kann, aber in Fällen, in denen dies nicht der Fall ist, kreischen kann.
Supercat
9

Platzieren Sie den out-Parameter in einer lokalen Variablen und setzen Sie die Variable dann in bd.Budget:

double tempVar = 0.0;

if (double.TryParse(objReader[i].ToString(), out tempVar))
{
    bd.Budget = tempVar;
}

Update : Direkt von MSDN:

Eigenschaften sind keine Variablen und können daher nicht als Out-Parameter übergeben werden.

Adam Houldsworth
quelle
1
@ E.vanderSpoel Zum Glück habe ich den Inhalt herausgehoben, den Link entfernt.
Adam Houldsworth
8

Möglicherweise von Interesse - Sie könnten Ihre eigenen schreiben:

    //double.TryParse(, out bd.Budget);
    bool result = TryParse(s, value => bd.Budget = value);
}

public bool TryParse(string s, Action<double> setValue)
{
    double value;
    var result =  double.TryParse(s, out value);
    if (result) setValue(value);
    return result;
}
David Hollinshead
quelle
5

Dies ist ein sehr alter Beitrag, aber ich ändere den akzeptierten, weil es einen noch bequemeren Weg gibt, dies zu tun, den ich nicht kannte.

Es heißt Inline-Deklaration und war möglicherweise immer verfügbar (wie bei der Verwendung von Anweisungen) oder wurde in solchen Fällen mit C # 6.0 oder C # 7.0 hinzugefügt, nicht sicher, funktioniert aber trotzdem wie ein Zauber:

Inetad davon

double temp;
double.TryParse(objReader[i].ToString(), out temp);
bd.Budget = temp;

benutze das:

double.TryParse(objReader[i].ToString(), out double temp);
bd.Budget = temp;
DanDan
quelle
2
Ich würde die Rückgabe verwenden, um zu überprüfen, ob die Analyse bei ungültiger Eingabe erfolgreich war.
MarcelDevG
1

Budget ist also eine Eigenschaft, richtig?

Setzen Sie es lieber zuerst auf eine lokale Variable und setzen Sie dann den Eigenschaftswert darauf.

double t = 0;
double.TryParse(objReader[i].ToString(), out t); 
bd.Budget = t;
Adriaan Stander
quelle
Danke. Aber darf ich wissen warum?
Pratik