2009-11-17 29 views
91

Utilizzo le proprietà implementate automaticamente. Immagino che il modo più veloce per risolvere il problema sia dichiarare la mia variabile di supporto? MessaggioImpossibile modificare l'errore del valore restituito C#

public Point Origin { get; set; } 

Origin.X = 10; // fails with CS1612 

Errore: Non è possibile modificare il valore di ritorno di 'espressione' perché non è una variabile

stato effettuato un tentativo di modificare un tipo di valore che è stato il risultato di un intermedio espressione. Poiché il valore non è persistente, il valore sarà invariato.

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

+12

Questo è un altro esempio del perché i tipi di valori mutabili sono una cattiva idea. Se puoi evitare di mutare un tipo di valore, fallo. –

+0

Prendere il seguente codice (dai miei sforzi a un'implementazione AStar scritto da un certo EL :-), che non poteva evitare di cambiare un tipo di valore: class Path : IEnumerable dove T: INode, new() {... } HexNode pubblico (int x, int y): questo (nuovo punto (x, y)) {} percorso percorso = nuovo percorso (nuovo T (x, y)); // Errore // Ugly fix Path path = new Path (new T()); path.LastStep.Centre = new Point (x, y); –

risposta

128

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

A causa di questo, quando si accede alla proprietà Origin si sta accedendo un copia del valore contenuto dalla classe, non il valore stesso come si farebbe con un tipo di riferimento (class), quindi se si imposta la X su di esso quindi si sta impostando la proprietà sulla copia e quindi scartandola, lasciando invariato il valore originale. Questo probabilmente non è ciò che intendevi, ed è per questo che il compilatore ti avverte.

Se si desidera modificare solo il valore X, è necessario fare qualcosa di simile:

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

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

+0

Questo è un po 'fiacco, perché l'assegnazione dei setter di proprietà ha un effetto collaterale (la struttura funge da vista in un tipo di riferimento di accompagnamento) – Alexander

+0

Un'altra soluzione è semplicemente rendere la tua struct in una classe. A differenza di C++, dove una classe e una struttura differiscono solo dall'accesso predefinito ai membri (privato e pubblico, rispettivamente), le strutture e le classi in C# presentano alcune differenze in più. Ecco alcune ulteriori informazioni: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/ – Artorias2718

6

L'utilizzo di una variabile di accompagnamento non è di aiuto. Il tipo Point è un tipo Valore.

è necessario assegnare l'intero valore Point alla proprietà Origine: -

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

Il problema è che quando si accede alla proprietà Origine ciò che viene restituito dal get in una copia della struttura Point Campo creato automaticamente dalle proprietà di origine. Quindi la tua modifica del campo X questa copia non influenzerebbe il campo sottostante. Il compilatore rileva questo e ti dà un errore poiché questa operazione è completamente inutile.

Anche se si è utilizzato il proprio appoggio variabile tua get sarà simile: -

get { return myOrigin; } 

saresti ancora restituendo una copia della struttura Point e si otterrebbe lo stesso errore.

Hmm ... dopo aver letto la tua domanda con più attenzione, forse in realtà intenzione di modificare la variabile supporto direttamente dal tuo classe: -

myOrigin.X = 10; 

Sì che sarebbe quello che si avrebbe bisogno.

0

Credo che il fermo qui è che si sta tentando di assegnare sotto-valori dell'oggetto nella dichiarazione piuttosto che assegnare l'oggetto stesso. È necessario assegnare l'intero oggetto Punto in questo caso poiché il tipo di proprietà è Punto.

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

Spero aveva senso ci

+2

Il punto importante è che Point è una struct (valore). Se fosse una classe (oggetto), allora il codice originale avrebbe funzionato. –

+0

Correctamundo: questa può essere una buona domanda per il colloquio. – MSIL

+0

@HansKesting: Se 'Point' fosse un tipo di classe mutabile, il codice originale avrebbe impostato il campo o la proprietà' X' nell'oggetto restituito dalla proprietà 'Origin'. Non vedo alcuna ragione per credere che avrebbe l'effetto desiderato sull'oggetto che contiene la proprietà 'Origin'. Alcune classi Framework hanno proprietà che copiano il loro stato in nuove istanze di classi mutabili e le restituiscono. Un tale progetto ha il vantaggio di consentire codice come 'thing1.Origin = thing2.Origin;' per impostare lo stato dell'origine dell'oggetto in modo che corrisponda a quello di un altro, ma non può avvisare il codice come 'thing1.Origin.X + = 4; '. – supercat

0

Il problema è che si punta a un valore situato sullo stack e il valore non sarà relfected indietro alla proprietà orignal modo C# non consente di restituire un riferimento a un tipo di valore. Penso che puoi risolvere questo problema rimuovendo la proprietà Origin e utilizzando invece un file pubblico, sì, so che non è una buona soluzione. L'altra soluzione è non usare il Punto e invece creare il proprio tipo di Punto come un oggetto.

+0

Se il 'Punto' è un membro di un tipo di riferimento, allora non sarà nello stack, sarà nell'heap nella memoria dell'oggetto contenente. –

4

Ormai si conosce già la causa dell'errore. Nel caso in cui un costruttore non esista con un sovraccarico per prendere la proprietà (in questo caso X), è possibile utilizzare l'inizializzatore dell'oggetto (che eseguirà tutta la magia dietro le quinte). Non che non c'è bisogno di fare i vostri le strutture immutabili, ma solo dare informazioni aggiuntive:

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; 

Questo appare come una cosa molto strana do, per niente consigliato. Basta elencare un modo alternativo. Il modo migliore per fare è rendere la struct immutabile e fornire un costruttore adeguato.

+2

Non eliminerebbe il valore di 'Origin.Y'? Data una proprietà di tipo 'Point', penserei che il modo idiomatico di cambiare solo' X' sarebbe 'var temp = thing.Origin; temp.X = 23; cosa.Origin = temp; '.L'approccio idiomatico ha il vantaggio che non deve menzionare i membri che non vuole modificare, una funzionalità che è possibile solo perché 'Point' è mutabile. Sono confuso dalla filosofia secondo cui il compilatore non può consentire 'Origin.X = 23;' si dovrebbe progettare una struttura per richiedere un codice come 'Origin.X = new Point (23, Origin.Y);' . Quest'ultimo aspetto mi sembra davvero icky. – supercat

+0

@supercat oh si, trascurato! Lo modificherò – nawfal

+0

@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ù semplice se C# non avesse fornito il costruttore predefinito per una struct di default (in questo caso devo assolutamente passare sia 'X' che' Y' al costruttore specifico). Ora perde il punto in cui si può fare 'Punto p = nuovo Punto()'. So perché è veramente necessario per una struttura, quindi non ha senso pensarlo. Ma hai una bella idea di aggiornare solo una proprietà come 'X'? – nawfal