Una proprietà o un indicizzatore non può essere passato come parametro out o ref


90

Ricevo l'errore precedente e non riesco a risolverlo. Ho cercato un po 'su Google ma non riesco a liberarmene.

Scenario:

Ho la classe BudgetAllocate la cui proprietà è budget che è di doppio tipo.

Nei miei datiAccessLayer,

In una delle mie classi sto cercando di fare questo:

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

Che sta generando questo errore:

La proprietà o l'indicizzatore non possono essere passati come parametro out o ref in fase di compilazione.

Ho anche provato questo:

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

Tutto il resto funziona bene e sono presenti riferimenti tra i livelli.


In bd.Budget bd è un oggetto della classe BudgetAllocate. Scusa ho dimenticato.
Pratik



1
Possibile duplicato delle proprietà
Legolas

Ho appena scoperto questo lavorando con un tipo di utente che aveva dei campi definiti che mi aspettavo DataGriddi popolare da poi sono venuto a sapere che è solo automatico con proprietà. Il passaggio alle proprietà ha rotto alcuni parametri di riferimento che stavo utilizzando nei miei campi. Devo definire le variabili locali con cui eseguire l'analisi.
jxramos

Risposte:


39

non puoi usare

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

sostituire bd.Budget con qualche variabile.

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

12
perché usare una variabile extra ??
Pratik

7
@pratik Non puoi passare una proprietà come parametro out perché non è garantito che la proprietà abbia effettivamente un setter, quindi hai bisogno della variabile extra.
matt

25
@ mjd79: il tuo ragionamento non è corretto. Il compilatore sa se esiste o meno un setter. Supponiamo che ci fosse un setter; dovrebbe essere consentito?
Eric Lippert

21
@dhinesh, penso che l'OP stia cercando una risposta sul perché non può farlo, non solo su cosa deve fare invece. Leggi la risposta di Hans Passant e i commenti di Eric Lippert.
slugster

2
@dhinesh Il motivo "reale" per cui non può farlo è perché sta usando C # invece di VB, il che lo consente. Vengo dal mondo VB (ovviamente?) E sono spesso sorpreso dalle restrizioni aggiuntive che C # impone.
SteveCinq

152

Altri ti hanno dato la soluzione, ma sul motivo per cui è necessario: una proprietà è solo zucchero sintattico per un metodo .

Ad esempio, quando dichiari una proprietà chiamata Namecon un getter e un setter, sotto il cofano il compilatore genera effettivamente metodi chiamati get_Name()eset_Name(value) . Quindi, quando si legge e si scrive in questa proprietà, il compilatore traduce queste operazioni in chiamate a quei metodi generati.

Quando lo consideri, diventa ovvio il motivo per cui non puoi passare una proprietà come parametro di output: in realtà passeresti un riferimento a un metodo , piuttosto che un riferimento a un oggetto una variabile , che è ciò che si aspetta un parametro di output.

Un caso simile esiste per gli indicizzatori.


19
Il tuo ragionamento è corretto fino all'ultimo pezzo. Il parametro out si aspetta un riferimento a una variabile , non a un oggetto .
Eric Lippert

@EricLippert ma una variabile non è anche un oggetto o cosa mi manca?
meJustAndrew

6
@meJustAndrew: Una variabile non è assolutamente un oggetto . Una variabile è una posizione di archiviazione . Una posizione di archiviazione contiene (1) un riferimento a un oggetto di tipo riferimento (o null) o (2) il valore di un oggetto di tipo valore. Non confondere il contenitore con la cosa in esso contenuta.
Eric Lippert

6
@meJustAndrew: considera un oggetto, diciamo, una casa. Considera un pezzo di carta su cui è scritto l'indirizzo della casa. Considera un cassetto che contiene quel pezzo di carta. Né il cassetto né la carta sono la casa .
Eric Lippert

69

Questo è un caso di astrazione che perde. Una proprietà è in realtà un metodo, il get and set funzioni di accesso per un indicizzatore vengono compilate nei metodi get_Index () e set_Index. Il compilatore fa un ottimo lavoro nascondendo questo fatto, ad esempio traduce automaticamente un'assegnazione a una proprietà nel metodo set_Xxx () corrispondente.

Ma questo va a monte quando si passa un parametro del metodo per riferimento. Ciò richiede che il compilatore JIT passi un puntatore alla posizione di memoria dell'argomento passato. Il problema è che non ce n'è uno, l'assegnazione del valore di una proprietà richiede la chiamata del metodo setter. Il metodo chiamato non può dire la differenza tra una variabile passata e una proprietà passata e quindi non può sapere se è richiesta una chiamata al metodo.

Notevole è che questo funziona effettivamente in VB.NET. Per esempio:

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

Il compilatore VB.NET risolve questo problema generando automaticamente questo codice per il metodo Run, espresso in C #:

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

Qual è la soluzione alternativa che puoi usare anche tu. Non sono sicuro del motivo per cui il team di C # non abbia utilizzato lo stesso approccio. Forse perché non volevano nascondere le chiamate getter e setter potenzialmente costose. O il comportamento completamente non diagnosticabile che otterrai quando il setter ha effetti collaterali che modificano il valore della proprietà, scompariranno dopo l'assegnazione. Differenza classica tra C # e VB.NET, C # è "nessuna sorpresa", VB.NET è "fallo funzionare se puoi".


16
Hai ragione sul non voler generare chiamate costose. Una ragione secondaria è che la semantica copy-in-copy-out ha una semantica diversa dalla semantica di riferimento e sarebbe incoerente avere due semantiche sottilmente diverse per il passaggio ref. (Detto questo, ci sono alcune rare situazioni in cui gli alberi delle espressioni compilati eseguono il copy-in-copy-out, purtroppo.)
Eric Lippert

2
Ciò che è veramente necessario è una più ampia varietà di modalità di passaggio dei parametri, in modo che il compilatore possa sostituire "copia in / copia fuori" dove appropriato, ma squawk nei casi in cui non lo è.
supercat

9

Posiziona il parametro out in una variabile locale e quindi imposta la variabile in bd.Budget:

double tempVar = 0.0;

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

Aggiornamento : direttamente da MSDN:

Le proprietà non sono variabili e quindi non possono essere passate come parametri out.


1
@ E.vanderSpoel Fortunatamente ho rimosso il contenuto, rimosso il collegamento.
Adam Houldsworth

8

Forse interessante - potresti scrivere il tuo:

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

5

Questo è un post molto vecchio, ma sto ammendendo l'accettato, perché c'è un modo ancora più comodo per farlo che non conoscevo.

Si chiama dichiarazione inline e potrebbe essere sempre stato disponibile (come nelle istruzioni using) o potrebbe essere stato aggiunto con C # 6.0 o C # 7.0 per questi casi, non sono sicuro, ma funziona comunque come un fascino:

Inetad di questo

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

Usa questo:

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

2
Vorrei utilizzare il ritorno per verificare se l'analisi è andata a buon fine in caso di input non valido.
MarcelDevG

2

Quindi Budget è una proprietà, giusto?

Piuttosto prima impostalo su una variabile locale, quindi imposta il valore della proprietà su quella.

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

Grazie, ma posso sapere perché?
Pratik

0

Di solito quando provo a farlo è perché voglio impostare la mia proprietà o lasciarla al valore predefinito. Con l'aiuto di questa risposta e dei dynamictipi possiamo facilmente creare un metodo di estensione delle stringhe per mantenerlo semplice e lineare.

public static dynamic ParseAny(this string text, Type type)
{
     var converter = TypeDescriptor.GetConverter(type);
     if (converter != null && converter.IsValid(text))
          return converter.ConvertFromString(text);
     else
          return Activator.CreateInstance(type);
}

Usa così;

bd.Budget = objReader[i].ToString().ParseAny(typeof(double));

// Examples
int intTest = "1234".ParseAny(typeof(int)); // Result: 1234
double doubleTest = "12.34".ParseAny(typeof(double)); // Result: 12.34
decimal pass = "12.34".ParseAny(typeof(decimal)); // Result: 12.34
decimal fail = "abc".ParseAny(typeof(decimal)); // Result: 0
string nullStr = null;
decimal failedNull = nullStr.ParseAny(typeof(decimal)); // Result: 0

Opzionale

In una nota a margine, se questo è un SQLDataReaderpuoi anche fare uso di GetSafeStringestensioni per evitare eccezioni nulle dal lettore.

public static string GetSafeString(this SqlDataReader reader, int colIndex)
{
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

public static string GetSafeString(this SqlDataReader reader, string colName)
{
     int colIndex = reader.GetOrdinal(colName);
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

Usa così;

bd.Budget = objReader.GetSafeString(i).ParseAny(typeof(double));
bd.Budget = objReader.GetSafeString("ColumnName").ParseAny(typeof(double));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.