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.