No se puede pasar una propiedad o un indexador como parámetro de salida o referencia

86

Recibo el error anterior y no puedo resolverlo. Busqué un poco en Google, pero no puedo deshacerme de él.

Guión:

Tengo clase BudgetAllocate cuya propiedad es presupuesto que es de tipo doble.

En mi dataAccessLayer,

En una de mis clases estoy tratando de hacer esto:

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

Que está arrojando este error:

La propiedad o el indexador no pueden pasarse como un parámetro de salida o de referencia en el momento de la compilación.

Incluso probé esto:

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

Todo lo demás funciona bien y las referencias entre capas están presentes.

Pratik
fuente
En bd.Budget bd es un objeto de la clase BudgetAllocate. Lo siento me olvidé.
Pratik
1
posible duplicado de las propiedades
Chris Moschini
1
Posible duplicado de propiedades
Legolas
Acabo de descubrir esto trabajando con un tipo de usuario que tenía campos definidos que se esperaba DataGridque rellenara y luego aprendí que solo autos con propiedades. Cambiar a propiedades rompió algunos parámetros de referencia que estaba usando en mis campos. Tengo que definir variables locales para realizar el análisis.
jxramos el

Respuestas:

37

no puede utilizar

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

reemplace bd.Budget con alguna variable.

double k;
double.TryParse(objReader[i].ToString(), out k); 
dhinesh
fuente
11
¿Por qué usar una variable adicional?
Pratik
6
@pratik No puede pasar una propiedad como un parámetro de salida porque no hay garantía de que la propiedad realmente tenga un establecedor, por lo que necesita la variable adicional.
Matt
23
@ mjd79: Tu razonamiento es incorrecto. El compilador sabe si hay un setter o no. Supongamos que hubiera un setter; ¿Debería estar permitido?
Eric Lippert
21
@dhinesh, creo que el OP está buscando una respuesta de por qué no puede hacerlo, no solo qué debe hacer en su lugar. Lea la respuesta de Hans Passant y los comentarios de Eric Lippert.
slugster
2
@dhinesh La razón "real" por la que no puede hacerlo es porque está usando C # en lugar de VB que SÍ permite esto. Soy del mundo VB (¿obviamente?) Y a menudo me sorprenden las restricciones adicionales que impone C #.
SteveCinq
149

Otros le han dado la solución, pero en cuanto a por qué es necesario: una propiedad es solo azúcar sintáctica para un método .

Por ejemplo, cuando declaras una propiedad llamada Namecon un getter y un setter, bajo el capó el compilador genera métodos llamados get_Name()y set_Name(value). Luego, cuando lee y escribe en esta propiedad, el compilador traduce estas operaciones en llamadas a esos métodos generados.

Cuando considera esto, resulta obvio por qué no puede pasar una propiedad como parámetro de salida: en realidad estaría pasando una referencia a un método , en lugar de una referencia a un objeto o una variable , que es lo que espera un parámetro de salida.

Existe un caso similar para los indexadores.

Mike Chamberlain
fuente
19
Tu razonamiento es correcto hasta el último bit. El parámetro out espera una referencia a una variable , no a un objeto .
Eric Lippert
@EricLippert, pero ¿no es una variable también un objeto o qué me estoy perdiendo?
meJustAndrew
6
@meJustAndrew: Una variable no es en absoluto un objeto . Una variable es una ubicación de almacenamiento . Una ubicación de almacenamiento contiene (1) una referencia a un objeto de tipo de referencia (o nulo), o (2) el valor de un objeto de tipo de valor. No confunda el recipiente con lo que contiene.
Eric Lippert
6
@meJustAndrew: Considere un objeto, digamos, una casa. Piense en una hoja de papel que tenga escrita la dirección de la casa. Piense en un cajón que contiene esa hoja de papel. Ni el cajón ni el papel son la casa .
Eric Lippert
69

Este es un caso de abstracción con fugas. Una propiedad es en realidad un método, los descriptores de acceso get y set para un indexador se compilan en los métodos get_Index () y set_Index. El compilador hace un trabajo estupendo ocultando ese hecho, automáticamente traduce una asignación a una propiedad al método set_Xxx () correspondiente, por ejemplo.

Pero esto se arruina cuando pasa un parámetro de método por referencia. Eso requiere que el compilador JIT pase un puntero a la ubicación de memoria del argumento pasado. El problema es que no hay uno, asignar el valor de una propiedad requiere llamar al método setter. El método llamado no puede diferenciar entre una variable pasada y una propiedad pasada y, por lo tanto, no puede saber si se requiere una llamada al método.

Es de destacar que esto realmente funciona en VB.NET. Por ejemplo:

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

El compilador VB.NET resuelve esto generando automáticamente este código para el método Run, expresado en C #:

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

Cuál es la solución alternativa que también puede utilizar. No estoy seguro de por qué el equipo de C # no usó el mismo enfoque. Posiblemente porque no querían ocultar las llamadas getter y setter potencialmente costosas. O el comportamiento completamente no diagnosticable que obtendrá cuando el colocador tenga efectos secundarios que cambien el valor de la propiedad, desaparecerán después de la asignación. Diferencia clásica entre C # y VB.NET, C # es "sin sorpresas", VB.NET es "haz que funcione si puedes".

Hans Passant
fuente
15
Tiene razón en no querer generar llamadas costosas. Una razón secundaria es que la semántica de copia dentro-copia-salida tiene una semántica diferente a la semántica de referencia y sería inconsistente tener dos semánticas sutilmente diferentes para pasar las referencias. (Dicho esto, hay algunas situaciones raras en las que los árboles de expresión compilados se copian dentro y fuera, desafortunadamente).
Eric Lippert
2
Lo que realmente se necesita es una mayor variedad de modos de paso de parámetros, de modo que el compilador pueda sustituir "copiar adentro / copiar fuera" cuando sea apropiado, pero chirriar en los casos en que no lo sea.
supercat
9

Coloque el parámetro out en una variable local y luego establezca la variable en bd.Budget:

double tempVar = 0.0;

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

Actualización : Directamente de MSDN:

Las propiedades no son variables y, por lo tanto, no se pueden pasar como parámetros.

Adam Houldsworth
fuente
1
@ E.vanderSpoel Afortunadamente, eliminé el contenido y eliminé el enlace.
Adam Houldsworth
8

Posiblemente de interés, podría escribir el suyo propio:

    //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
fuente
5

Esta es una publicación muy antigua, pero estoy enmendando la aceptada, porque hay una forma aún más conveniente de hacer esto que yo no conocía.

Se llama declaración en línea y es posible que siempre haya estado disponible (como en el uso de declaraciones) o podría haberse agregado con C # 6.0 o C # 7.0 para tales casos, no estoy seguro, pero funciona como un encanto de todos modos:

Inetad de esto

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

utilizar esta:

double.TryParse(objReader[i].ToString(), out double temp);
bd.Budget = temp;
DanDan
fuente
2
Usaría la devolución para verificar si el análisis fue exitoso en caso de una entrada no válida.
MarcelDevG
1

Entonces Budget es una propiedad, ¿correcto?

En lugar de eso, primero configúrelo en una variable local y luego establezca el valor de propiedad en eso.

double t = 0;
double.TryParse(objReader[i].ToString(), out t); 
bd.Budget = t;
Adriaan Stander
fuente
Gracias, pero ¿puedo saber por qué?
Pratik
0

Por lo general, cuando intento hacer esto es porque quiero configurar mi propiedad o dejarla en el valor predeterminado. Con la ayuda de esta respuesta y dynamictipos, podemos crear fácilmente un método de extensión de cadena para mantenerlo simple y con líneas.

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

Úselo así;

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

Opcional

En una nota al margen, si es SQLDataReaderasí, también puede hacer uso de las GetSafeStringextensiones para evitar excepciones nulas por parte del lector.

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

Úselo así;

bd.Budget = objReader.GetSafeString(i).ParseAny(typeof(double));
bd.Budget = objReader.GetSafeString("ColumnName").ParseAny(typeof(double));
clamchoda
fuente