2009-11-17 15 views
91

estoy usando propiedades implementadas automáticamente. supongo que la forma más rápida para fijar siguiente es para declarar mi propia variable respaldo? MensajeNo se puede modificar el valor de retorno de error C#

public Point Origin { get; set; } 

Origin.X = 10; // fails with CS1612 

Error: No se puede modificar el valor de retorno de la 'expresión' porque no es una variable

Se hizo un intento de modificar un tipo de valor que fue el resultado de un intermedio expresión. Como el valor no se conserva, el valor no cambiará.

Para resolver este error, almacenar el resultado de la expresión en un valor intermedio, o bien utiliza un tipo de referencia para la expresión intermedio.

+12

Esta es otra ilustración de por qué los tipos de valores mutables son una mala idea. Si puede evitar la mutación de un tipo de valor, hágalo. –

+0

Tome el siguiente código (de mis esfuerzos en una implementación de AStar blogueado por un cierto EL :-), que no pudo evitar cambiar un tipo de valor: class Path : IEnumerable donde T: INode, new() {... } public HexNode (int x, int y): this (new Point (x, y)) {} Ruta path = new Ruta (nueva T (x, y)); // Error // Ugly fix Ruta path = new Ruta (nueva T()); path.LastStep.Centre = new Point (x, y); –

Respuesta

128

Esto es porque Point es un tipo de valor (struct).

Debido a esto, cuando se accede a la propiedad Origin que está accediendo a una copia del valor en poder de la clase, no el valor en sí como lo haría con un tipo de referencia (class), por lo que si se establece la X propiedad en ella, luego se está configurando la propiedad en la copia y luego descartarlo, dejando el valor original sin cambios. Probablemente esto no sea lo que pretendías, y es por eso que el compilador te advierte al respecto.

Si desea cambiar sólo el valor X, tiene que hacer algo como esto:

Origin = new Point(10, Origin.Y); 
+1

@Paul: ¿Tienes la capacidad de cambiar la estructura a una clase? – Doug

+0

Esto es un poco fastidioso, porque la asignación im setter de propiedad tiene un efecto secundario (la estructura actúa como una vista en un tipo de referencia de referencia) – Alexander

+0

Otra solución es simplemente hacer que su estructura en una clase. A diferencia de C++, donde una clase y una estructura difieren solo por el acceso de miembro predeterminado (privado y público, respectivamente), las estructuras y las clases en C# tienen algunas diferencias más. Aquí hay más información: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/ – Artorias2718

6

Usando una variable respaldo no ayudará. El tipo Point es un tipo de valor.

necesita asignar todo el valor del punto a la propiedad Origen: -

Origin = new Point(10, Origin.Y); 

El problema es que cuando se accede a la propiedad Origen lo que se devuelve por el get es una copia de la estructura de puntos en el Campo autodirigido de propiedades de origen. De ahí su modificación del campo X esta copia no afectaría el campo subyacente. El compilador detecta esto y le da un error ya que esta operación es completamente inútil.

Incluso si usted utiliza su propia variable respaldo de su get se vería así: -

get { return myOrigin; } 

todavía serías devolviendo una copia de la estructura de puntos y obtendría el mismo error.

Hmm ... después de leer su pregunta quizá con más cuidado que en realidad significa para modificar la variable de respaldo directamente desde dentro de su clase: -

myOrigin.X = 10; 

Sí eso sería lo que se necesita.

0

supongo que el truco aquí es que usted está intentando asignar sub-valores del objeto en el estado en lugar de asignando el objeto mismo. Debe asignar todo el objeto Point en este caso ya que el tipo de propiedad es Point.

Point newOrigin = new Point(10, 10); 
Origin = newOrigin; 

esperanza que tenía sentido hay

+2

El punto importante es que Point es una estructura (valuetype). Si se tratara de una clase (objeto), entonces el código original habría funcionado. –

+0

Correctamundo: esta puede ser una buena pregunta para una entrevista. – MSIL

+0

@HansKesting: si 'Point' era un tipo de clase mutable, el código original habría establecido el campo o la propiedad' X' en el objeto devuelto por la propiedad 'Origin'. No veo ninguna razón para creer que tendría el efecto deseado sobre el objeto que contiene la propiedad 'Origin'. Algunas clases de Framework tienen propiedades que copian su estado a nuevas instancias de clases mutables y las devuelven. Tal diseño tiene la ventaja de permitir código como 'thing1.Origin = thing2.Origin;' para establecer el estado del origen del objeto para que coincida con el de otro, pero no puede advertir sobre el código como 'thing1.Origin.X + = 4; '. – supercat

0

El problema es que usted señala a un valor situado en la pila y el valor no se relfected de nuevo a la propiedad orignal lo que C# no permite que regrese una referencia a un tipo de valor Creo que puedes resolver esto quitando la propiedad Origin y en su lugar usar un archivo público, sí, sé que no es una buena solución. La otra solución es no usar el Punto, y en su lugar crear su propio Tipo de Punto como un objeto.

+0

Si el 'Punto' es un miembro de un tipo de referencia, entonces no estará en la pila, estará en el montón en la memoria del objeto que lo contiene. –

4

Por ahora ya sabe cuál es la fuente del error. En caso de que no exista un constructor con una sobrecarga para tomar su propiedad (en este caso X), puede usar el inicializador de objetos (que hará toda la magia detrás de las escenas). No es que no es necesario que haga sus estructuras inmutables, pero sólo dar información adicional:

struct Point 
{ 
    public int X { get; set; } 
    public int Y { get; set; } 
} 

class MyClass 
{ 
    public Point Origin { get; set; } 
} 

MyClass c = new MyClass(); 
c.Origin.X = 23; //fails. 

//but you could do: 
c.Origin = new Point { X = 23, Y = c.Origin.Y }; //though you are invoking default constructor 

//instead of 
c.Origin = new Point(23, c.Origin.Y); //in case there is no constructor like this. 

Esto es posible porque detrás de las escenas que esto sucede:

Point tmp = new Point(); 
tmp.X = 23; 
tmp.Y = Origin.Y; 
c.Origin = tmp; 

Esto parece una cosa muy extraña do, en absoluto recomendado. Solo haciendo una lista de una manera alternativa. La mejor forma de hacerlo es hacer que la estructura sea inmutable y proporcionar un constructor adecuado.

+2

¿Eso no destruiría el valor de 'Origin.Y'? Dada una propiedad de tipo 'Point', creo que la forma idiomática de cambiar simplemente' X' sería 'var temp = thing.Origin; temp.X = 23; thing.Origin = temp; '.El enfoque idiomático tiene la ventaja de que no tiene que mencionar los miembros que no desea modificar, una característica que solo es posible porque 'Point' es mutable. Estoy desconcertado con la filosofía que dice que debido a que el compilador no puede permitir 'Origin.X = 23;' uno debe diseñar una estructura para requerir código como 'Origin.X = new Point (23, Origin.Y);' . Esto último me parece realmente repugnante. – supercat

+0

@supercat oh sí, se pasó por alto eso! Voy a editarlo – nawfal

+0

@supercat esta es la primera vez que pienso en tu punto, ** tiene mucho sentido! ** ¿Tienes una idea alternativa de patrón/diseño para abordar esto? Hubiera sido más fácil si C# no hubiera proporcionado el constructor predeterminado para una estructura por defecto (en ese caso, tengo que pasar estrictamente tanto 'X' como' Y' a un constructor específico). Ahora pierde el punto cuando uno puede hacer 'Point p = new Point()'. Sé por qué es realmente necesario para una estructura, así que no tiene sentido pensar en eso. ¿Pero tiene una buena idea actualizar solo una propiedad como 'X'? – nawfal

Cuestiones relacionadas