Errore: "Impossibile modificare il valore restituito" c #


155

Sto usando le proprietà implementate automaticamente. Immagino che il modo più veloce per risolvere il problema sia quello di dichiarare la mia variabile di supporto?

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

Messaggio di errore: impossibile modificare il valore restituito di "espressione" perché non è una variabile

È stato effettuato un tentativo di modificare un tipo di valore che era il risultato di un'espressione intermedia. Poiché il valore non è persistente, il valore rimarrà invariato.

Per risolvere questo errore, archiviare il risultato dell'espressione in un valore intermedio o utilizzare un tipo di riferimento per l'espressione intermedia.


13
Questo è un altro esempio del perché i tipi di valori mutabili sono una cattiva idea. Se riesci ad evitare di mutare un tipo di valore, fallo.
Eric Lippert,

Prendi il seguente codice (dai miei sforzi per un'implementazione di AStar bloggata da un certo EL :-), che non poteva evitare di cambiare un tipo di valore: class Path <T>: IEnumerable <T> dove T: INode, new () {. ..} public HexNode (int x, int y): this (new Point (x, y)) {} Path <T> path = new Path <T> (new T (x, y)); // Errore // Ugly fix Path <T> path = new Path <T> (new T ()); path.LastStep.Centre = new Point (x, y);
Tom Wilson,

Risposte:


198

Questo perché Pointè un tipo di valore ( struct).

Per questo motivo, quando si accede alla Originproprietà si accede a una copia del valore posseduto dalla classe, non al valore stesso come si farebbe con un tipo di riferimento ( class), quindi se si imposta la Xproprietà su di essa, si sta impostando la proprietà sulla copia e quindi scartandola, lasciando invariato il valore originale. Questo probabilmente non è quello che volevi, motivo per cui il compilatore ti sta avvisando.

Se vuoi cambiare solo il Xvalore, devi fare qualcosa del genere:

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

2
@Paul: hai la possibilità di cambiare la struttura in una classe?
Doug,

1
Questo è un po 'un peccato, perché il setter di proprietà che sto assegnando ha un effetto collaterale (la struttura funge da vista in un tipo di riferimento di supporto)
Alexander - Reinstate Monica

Un'altra soluzione è semplicemente trasformare la tua struttura in una classe. A differenza di C ++, dove una classe e una struttura differiscono solo per l'accesso predefinito dei membri (rispettivamente privato e pubblico), le strutture e le classi in C # presentano alcune differenze in più. Ecco alcune informazioni in più: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Artorias2718

9

L'uso di una variabile di supporto non aiuta. Il Pointtipo è un tipo Valore.

È necessario assegnare l'intero valore Point alla proprietà Origin: -

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

Il problema è che quando si accede alla proprietà Origin ciò che viene restituito da getè una copia della struttura Point nel campo creato automaticamente delle proprietà Origin. Quindi la tua modifica del campo X questa copia non influirebbe sul campo sottostante. Il compilatore rileva questo e ti dà un errore poiché questa operazione è completamente inutile.

Anche se hai utilizzato la tua variabile di supporto get, assomiglieresti a:

get { return myOrigin; }

Restituiresti comunque una copia della struttura Point e otterrai lo stesso errore.

Hmm ... avendo letto più attentamente la tua domanda, forse intendi effettivamente modificare la variabile di supporto direttamente dalla tua classe: -

myOrigin.X = 10;

Sì, sarebbe quello di cui avresti bisogno.


6

Ormai sai già qual è l'origine dell'errore. Nel caso in cui un costruttore non esista con un sovraccarico per prendere la tua proprietà (in questo caso X), puoi usare l'inizializzatore di oggetti (che farà tutta la magia dietro le quinte). Non che non sia necessario rendere immutabili le tue strutture , ma semplicemente dare ulteriori informazioni:

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.

Questo è possibile perché dietro le quinte questo accade:

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

Sembra una cosa molto strana da fare, per niente consigliata. Elencando solo un modo alternativo. Il modo migliore per farlo è rendere immutabile la struttura e fornire un costruttore adeguato.


2
Non eliminerebbe il valore di Origin.Y? Data una proprietà di tipo Point, penso che il modo idiomatico di cambiare Xsarebbe proprio var temp=thing.Origin; temp.X = 23; thing.Origin = temp;. L'approccio idiomatico ha il vantaggio di non dover menzionare i membri che non vuole modificare, una caratteristica che è possibile solo perché Pointè mutevole. Sono sconcertato dalla filosofia che afferma che, poiché il compilatore non può consentire, Origin.X = 23;si dovrebbe progettare una struttura che richieda codice simile Origin.X = new Point(23, Origin.Y);. Quest'ultimo mi sembra davvero icky.
supercat

@supercat questa è la prima volta che penso al tuo punto, ha molto senso! Hai un modello alternativo / idea di design per affrontare questo? Sarebbe stato più facile avuto C # non ha fornito il costruttore predefinito per una struttura di default (in questo caso ho strettamente devo passare sia Xe Yal costruttore specifico). Ora perde il punto in cui si può fare Point p = new Point(). So perché è davvero necessario per una struttura, quindi non ha senso pensare ad essa. Ma hai una buona idea aggiornare solo una proprietà come X?
nawfal,

Per le strutture che incapsulano una raccolta di variabili indipendenti ma correlate (come le coordinate di un punto), la mia preferenza è semplicemente fare in modo che la struttura esponga tutti i suoi membri come campi pubblici; per modificare un membro di una proprietà struct, basta leggerlo, modificare il membro e riscriverlo. Sarebbe stato bello se C # avesse fornito una dichiarazione "Semplice-Vecchio-Data-Struct" che avrebbe automaticamente definito un costruttore il cui elenco di parametri corrispondeva all'elenco dei campi, ma le persone responsabili di C # disprezzano le strutture mutabili.
supercat

@supercat Ho capito. Il comportamento incoerente di strutture e classi è confuso.
nawfal,

La confusione deriva dalla convinzione inutile dell'IMO che tutto dovrebbe comportarsi come un oggetto di classe. Sebbene sia utile disporre di un mezzo per passare valori di tipo valore a cose che prevedono riferimenti a oggetti heap, non è utile fingere che le variabili di tipo valore contengano elementi che derivano daObject . Non lo fanno. Ogni definizione di tipo di valore definisce in realtà due tipi di cose: un tipo di posizione di archiviazione (usato per variabili, slot di array, ecc.) E un tipo di oggetto heap, a volte indicato come un tipo "boxed" (usato quando un valore di tipo valore è memorizzato in una posizione del tipo di riferimento).
supercat

2

Oltre a discutere i pro ei contro delle strutture rispetto alle classi, tendo a guardare l'obiettivo e ad affrontare il problema da quella prospettiva.

Detto questo, se non hai bisogno di scrivere codice dietro la proprietà get e impostare metodi (come nel tuo esempio), non sarebbe più semplice dichiarare semplicemente il Origincampo della classe piuttosto che una proprietà? Dovrei pensare che questo ti consentirebbe di raggiungere il tuo obiettivo.

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

Il problema è che si punta a un valore situato nello stack e il valore non verrà ricollegato alla proprietà originale, quindi C # non consente di restituire un riferimento a un tipo di valore. Penso che puoi risolverlo rimuovendo la proprietà Origin e invece utilizzare un archivio pubblico, sì, lo so che non è una buona soluzione. L'altra soluzione è di non utilizzare il punto e creare invece il proprio tipo di punto come oggetto.


Se Pointè un membro di un tipo di riferimento, non sarà nello stack, sarà nell'heap nella memoria dell'oggetto contenitore.
Greg Beech,

0

Immagino che il problema qui sia che stai provando ad assegnare i sotto-valori dell'oggetto nell'istruzione invece di assegnare l'oggetto stesso. In questo caso è necessario assegnare l'intero oggetto Point poiché il tipo di proprietà è Point.

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

Spero di avere un senso lì


2
Il punto importante è che Point è una struttura (tipo di valore). Se fosse una classe (oggetto), il codice originale avrebbe funzionato.
Hans Ke il

@HansKesting: se Pointfosse un tipo di classe mutabile, il codice originale avrebbe impostato il campo o la proprietà Xnell'oggetto restituito dalla proprietà Origin. Non vedo alcun motivo per credere che avrebbe l'effetto desiderato sull'oggetto che contiene la Originproprietà. Alcune classi Framework hanno proprietà che copiano il loro stato in nuove istanze di classe mutabili e le restituiscono. Una tale progettazione ha il vantaggio di consentire al codice simile thing1.Origin = thing2.Origin;di impostare lo stato dell'origine dell'oggetto in modo che corrisponda a quello di un altro, ma non può avvisare di codice simile thing1.Origin.X += 4;.
supercat

0

Basta rimuovere la proprietà "get set" come segue, quindi tutto funziona come sempre.

In caso di tipi primitivi, utilizzare il comando 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

Penso che molte persone si stiano confondendo qui, questo particolare problema è legato alla comprensione del fatto che le proprietà del tipo di valore restituiscono una copia del tipo di valore (come con metodi e indicizzatori) e l' accesso ai campi del tipo di valore direttamente . Il codice seguente fa esattamente quello che stai cercando di ottenere accedendo direttamente al campo di supporto della proprietà (nota: esprimere una proprietà nella sua forma dettagliata con un campo di supporto è l'equivalente di una proprietà auto, ma ha il vantaggio che nel nostro codice possiamo accedere direttamente al campo di supporto):

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

L'errore che si sta verificando è una conseguenza indiretta della mancata comprensione del fatto che una proprietà restituisce una copia di un tipo di valore. Se ti viene restituita una copia di un tipo di valore e non la assegni a una variabile locale, le eventuali modifiche apportate a tale copia non potranno mai essere lette e pertanto il compilatore genera questo come errore poiché non può essere intenzionale. Se assegniamo la copia a una variabile locale, allora possiamo cambiare il valore di X, ma verrà modificato solo sulla copia locale, che corregge l'errore di compilazione, ma non avrà l'effetto desiderato di modificare la proprietà Origin. Il seguente codice illustra questo, poiché l'errore di compilazione è sparito, ma l'asserzione di debug non riuscirà:

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