Error: "No se puede modificar el valor de retorno" c #


155

Estoy usando propiedades implementadas automáticamente. ¿Supongo que la forma más rápida de arreglar lo siguiente es declarar mi propia variable de respaldo?

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

Mensaje de error: no se puede modificar el valor de retorno de 'expresión' porque no es una variable

Se intentó modificar un tipo de valor que fue el resultado de una expresión intermedia. Debido a que el valor no persiste, el valor no cambiará.

Para resolver este error, almacene el resultado de la expresión en un valor intermedio o use un tipo de referencia para la expresión intermedia.


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

Tome el siguiente código (de mis esfuerzos en una implementación de AStar blogueada por un cierto EL :-), que no pudo evitar cambiar un tipo de valor: class Path <T>: IEnumerable <T> donde T: INode, new () {. ..} Public HexNode (int x, int y): this (new Point (x, y)) {} Path <T> path = new Path <T> (new T (x, y)); // Error // Corrección fea Ruta <T> ruta = nueva Ruta <T> (nueva T ()); path.LastStep.Centre = nuevo punto (x, y);
Tom Wilson

Respuestas:


198

Esto se debe a que Pointes un tipo de valor ( struct).

Debido a esto, cuando accede a la Originpropiedad está accediendo a una copia del valor que posee la clase, no al valor en sí como lo haría con un tipo de referencia (class ), por lo que si establece el valorX propiedad en él, entonces está configurando la propiedad en la copia y luego descartarla, dejando el valor original sin cambios. Probablemente esto no sea lo que pretendía, por eso el compilador le está advirtiendo al respecto.

Si desea cambiar solo el Xvalor, debe hacer algo como esto:

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

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

1
Esto es un fastidio, porque el establecedor de propiedades que estoy asignando tiene un efecto secundario (la estructura actúa como una vista en un tipo de referencia de respaldo)
Alexander - Restablece a Monica

Otra solución es simplemente convertir 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: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Artorias2718

9

Usar una variable de respaldo no ayudará. El Pointtipo es un tipo de valor.

Debe asignar todo el valor del Punto a la propiedad Origen: -

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

El problema es que cuando accede a la propiedad Origen, lo que devuelve el get es una copia de la estructura Punto en el campo creado automáticamente de las propiedades Origen. Por lo tanto, 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 usara su propia variable de respaldo, getse vería así:

get { return myOrigin; }

Seguiría devolviendo una copia de la estructura de puntos y obtendría el mismo error.

Hmm ... habiendo leído tu pregunta con más cuidado, tal vez realmente quieras modificar la variable de respaldo directamente desde tu clase:

myOrigin.X = 10;

Sí, eso sería lo que necesitarías.


6

Por ahora ya sabes 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 escena). No es que no necesite hacer que sus estructuras sean inmutables , sino solo 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 escena sucede esto:

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

Esto parece una cosa muy extraña, no se recomienda en absoluto. Solo enumerando una forma alternativa. La mejor manera de hacerlo es hacer que struct sea inmutable y proporcionar un constructor adecuado.


2
¿No acabaría con eso el valor de Origin.Y? Dada una propiedad de tipo Point, creo que la forma idiomática de cambiar Xsería var temp=thing.Origin; temp.X = 23; thing.Origin = temp;. El enfoque idiomático tiene la ventaja de que no tiene que mencionar a los miembros que no desea modificar, una característica que solo es posible porque Pointes mutable. Estoy desconcertado con la filosofía que dice que debido a que el compilador no puede permitir que Origin.X = 23;uno deba diseñar una estructura que requiera un código similar Origin.X = new Point(23, Origin.Y);. Esto último me parece realmente asqueroso.
supercat

@supercat esta es la primera vez que pienso en tu punto, ¡ tiene mucho sentido! ¿Tiene una idea alternativa de diseño / patrón para abordar esto? Se habría tenido más fácil de C # no proporcionada al constructor por defecto para una estructura de forma predeterminada (en ese caso estrictamente que pasar tanto Xy Yal 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 ello. ¿Pero tienes una idea genial para actualizar solo una propiedad X?
nawfal

Para las estructuras que encapsulan una colección de variables independientes pero relacionadas (como las coordenadas de un punto), prefiero que la estructura exponga a todos sus miembros como campos públicos; para modificar un miembro de una propiedad de estructura, simplemente léalo, modifique el miembro y escríbalo de nuevo. Hubiera sido bueno que C # hubiera proporcionado una declaración de "Estructura simple de datos antiguos" que definiría automáticamente un constructor cuya lista de parámetros coincidiera con la lista de campos, pero las personas responsables de C # desprecian las estructuras mutables.
supercat

@supercat lo entiendo. El comportamiento inconsistente de estructuras y clases es confuso.
nawfal

La confusión resulta de la creencia inútil de la OMI de que todo debería comportarse como un objeto de clase. Si bien es útil tener un medio de pasar valores de tipo de valor a cosas que esperan referencias de objetos de montón, no es útil pretender que las variables de tipo de valor contienen cosas que se derivan Object. Ellos no. Cada definición de tipo de valor en realidad define dos tipos de cosas: un tipo de ubicación de almacenamiento (utilizado para variables, ranuras de matriz, etc.) y un tipo de objeto de almacenamiento dinámico, a veces denominado tipo "en caja" (utilizado cuando un valor de tipo de valor se almacena en una ubicación de tipo de referencia).
supercat

2

Además de debatir los pros y los contras de las estructuras versus las clases, tiendo a mirar el objetivo y abordar el problema desde esa perspectiva.

Dicho esto, si no necesita escribir código detrás de los métodos get y set de la propiedad (como en su ejemplo), ¿no sería más fácil simplemente declararlo Origincomo un campo de la clase en lugar de una propiedad? Creo que esto te permitiría lograr tu objetivo.

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

class MyClass
{
    public Point Origin;
}

MyClass c = new MyClass();
c.Origin.X = 23;   // No error.  Sets X just fine

0

El problema es que apunta a un valor ubicado en la pila y el valor no se volverá a proteger a la propiedad original, por lo que C # no le permite devolver una referencia a un tipo de valor. Creo que puede resolver esto eliminando 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 objeto.


Si el Pointes 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.
Greg Beech

0

Supongo que el problema aquí es que estás tratando de asignar subvalores de objeto en la declaración en lugar de asignar el objeto en sí. 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;

Espero tener sentido allí


2
El punto importante es que Point es una estructura (valuetype). Si fuera una clase (objeto), entonces el código original habría funcionado.
Hans Keing ing

@HansKesting: si Pointfuera un tipo de clase mutable, el código original habría establecido un campo o propiedad Xen 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 Originpropiedad. Algunas clases de Framework tienen propiedades que copian su estado en nuevas instancias de clase mutable y las devuelven. Tal diseño tiene la ventaja de permitir que el código thing1.Origin = thing2.Origin;establezca el estado del origen del objeto para que coincida con el de otro, pero no puede advertir sobre el código thing1.Origin.X += 4;.
supercat

0

Simplemente elimine la propiedad "get set" de la siguiente manera, y luego todo funciona como siempre.

En el caso de los tipos primitivos instread, use get; set; ...

using Microsoft.Xna.Framework;
using System;

namespace DL
{
    [Serializable()]
    public class CameraProperty
    {
        #region [READONLY PROPERTIES]
        public static readonly string CameraPropertyVersion = "v1.00";
        #endregion [READONLY PROPERTIES]


        /// <summary>
        /// CONSTRUCTOR
        /// </summary>
        public CameraProperty() {
            // INIT
            Scrolling               = 0f;
            CameraPos               = new Vector2(0f, 0f);
        }
        #region [PROPERTIES]   

        /// <summary>
        /// Scrolling
        /// </summary>
        public float Scrolling { get; set; }

        /// <summary>
        /// Position of the camera
        /// </summary>
        public Vector2 CameraPos;
        // instead of: public Vector2 CameraPos { get; set; }

        #endregion [PROPERTIES]

    }
}      

0

Creo que mucha gente se está confundiendo aquí, este problema en particular está relacionado con la comprensión de que las propiedades de tipo de valor devuelven una copia del tipo de valor (como con los métodos e indexadores), y se accede directamente a los campos de tipo de valor . El siguiente código hace exactamente lo que está tratando de lograr accediendo directamente al campo de respaldo de la propiedad (nota: expresar una propiedad en su forma detallada con un campo de respaldo es el equivalente de una propiedad automática, pero tiene la ventaja de que en nuestro código podemos acceder al campo de respaldo directamente):

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        myClass.SetOrigin();
        Debug.Assert(myClass.Origin.X == 10); //succeeds
    }
}

class MyClass
{
    private Point _origin;
    public Point Origin
    { 
        get => _origin; 
        set => _origin = value; 
    }

    public void SetOrigin()
    {
        _origin.X = 10; //this works
        //Origin.X = 10; // fails with CS1612;
    }
}

El error que está obteniendo es una consecuencia indirecta de no entender que una propiedad devuelve una copia de un tipo de valor. Si se le devuelve una copia de un tipo de valor y no la asigna a una variable local, los cambios que realice en esa copia nunca podrán leerse y, por lo tanto, el compilador plantea esto como un error, ya que esto no puede ser intencional. Si asignamos la copia a una variable local, entonces podemos cambiar el valor de X, pero solo se cambiará en la copia local, que corrige el error de tiempo de compilación, pero no tendrá el efecto deseado de modificar la propiedad Origen. El siguiente código ilustra esto, ya que el error de compilación desapareció, pero la afirmación de depuración fallará:

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        myClass.SetOrigin();
        Debug.Assert(myClass.Origin.X == 10); //throws error
    }
}

class MyClass
{
    private Point _origin;
    public Point Origin
    { 
        get => _origin; 
        set => _origin = value; 
    }

    public void SetOrigin()
    {
        var origin = Origin;
        origin.X = 10; //this is only changing the value of the local copy
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.