Fehler: "Rückgabewert kann nicht geändert werden" c #

155

Ich verwende automatisch implementierte Eigenschaften. Ich denke, der schnellste Weg, um Folgendes zu beheben, besteht darin, meine eigene Hintergrundvariable zu deklarieren.

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

Fehlermeldung: Der Rückgabewert von 'Ausdruck' kann nicht geändert werden, da es sich nicht um eine Variable handelt

Es wurde versucht, einen Werttyp zu ändern, der das Ergebnis eines Zwischenausdrucks war. Da der Wert nicht beibehalten wird, bleibt der Wert unverändert.

Um diesen Fehler zu beheben, speichern Sie das Ergebnis des Ausdrucks in einem Zwischenwert oder verwenden Sie einen Referenztyp für den Zwischenausdruck.

P aul
quelle
13
Dies ist ein weiteres Beispiel dafür, warum veränderbare Werttypen eine schlechte Idee sind. Wenn Sie vermeiden können, einen Werttyp zu mutieren, tun Sie dies.
Eric Lippert
Nehmen Sie den folgenden Code (aus meinen Bemühungen um eine AStar-Implementierung, die von einer bestimmten EL gebloggt wurde :-), die es nicht vermeiden konnte, einen Werttyp zu ändern: class Path <T>: IEnumerable <T> wobei T: INode, new () {. ..} public HexNode (int x, int y): this (neuer Punkt (x, y)) {} Pfad <T> Pfad = neuer Pfad <T> (neues T (x, y)); // Fehler // Hässlicher Fixpfad <T> Pfad = neuer Pfad <T> (neues T ()); path.LastStep.Centre = neuer Punkt (x, y);
Tom Wilson

Antworten:

197

Dies liegt daran, dass Pointes sich um einen Werttyp ( struct) handelt.

Wenn Sie auf die OriginEigenschaft zugreifen , greifen Sie daher auf eine Kopie des Werts zu, der von der Klasse gehalten wird, und nicht auf den Wert selbst, wie Sie es mit einem Referenztyp ( class) tun würden. Wenn Sie also die XEigenschaft darauf festlegen, legen Sie fest die Eigenschaft auf der Kopie und verwerfen Sie sie dann, wobei der ursprüngliche Wert unverändert bleibt. Dies ist wahrscheinlich nicht das, was Sie beabsichtigt haben, weshalb der Compiler Sie davor warnt.

Wenn Sie nur den XWert ändern möchten , müssen Sie Folgendes tun:

Origin = new Point(10, Origin.Y);
Greg Beech
quelle
2
@ Paul: Haben Sie die Möglichkeit, die Struktur in eine Klasse zu ändern?
Doug
1
Dies ist eine Art Patzer, weil der zugewiesene Eigenschaftssetzer einen Nebeneffekt hat (die Struktur fungiert als Blick in einen Hintergrundreferenztyp)
Alexander - Reinstate Monica
Eine andere Lösung besteht darin, Ihre Struktur einfach zu einer Klasse zu machen. Anders als in C ++, wo sich eine Klasse und eine Struktur nur durch den Standardzugriff auf Mitglieder (privat bzw. öffentlich) unterscheiden, weisen Strukturen und Klassen in C # einige weitere Unterschiede auf. Hier sind einige weitere Informationen: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Artorias2718
9

Die Verwendung einer Hintergrundvariablen hilft nicht weiter. Der PointTyp ist ein Werttyp.

Sie müssen der Origin-Eigenschaft den gesamten Punktwert zuweisen: -

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

Das Problem besteht darin, dass beim Zugriff auf die Origin-Eigenschaft geteine Kopie der Punktstruktur im automatisch erstellten Feld Origin-Eigenschaften zurückgegeben wird. Daher würde Ihre Änderung des X-Felds dieser Kopie das zugrunde liegende Feld nicht beeinflussen. Der Compiler erkennt dies und gibt einen Fehler aus, da dieser Vorgang völlig nutzlos ist.

Selbst wenn Sie Ihre eigene Hintergrundvariable verwenden getwürden, würde dies folgendermaßen aussehen:

get { return myOrigin; }

Sie würden immer noch eine Kopie der Punktstruktur zurückgeben und würden den gleichen Fehler erhalten.

Hmm ... nachdem Sie Ihre Frage genauer gelesen haben, möchten Sie vielleicht tatsächlich die Hintergrundvariable direkt aus Ihrer Klasse heraus ändern: -

myOrigin.X = 10;

Ja, das wäre was du brauchst.

AnthonyWJones
quelle
6

Inzwischen wissen Sie bereits, woher der Fehler stammt. Falls es keinen Konstruktor mit einer Überladung gibt, um Ihre Eigenschaft zu übernehmen (in diesem Fall X), können Sie den Objektinitialisierer verwenden (der die ganze Magie hinter den Kulissen ausführt). Nicht, dass Sie Ihre Strukturen nicht unveränderlich machen müssen , sondern nur zusätzliche Informationen geben müssen:

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.

Dies ist möglich, weil dies hinter den Kulissen geschieht:

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

Dies scheint eine sehr seltsame Sache zu sein, die überhaupt nicht empfohlen wird. Listen Sie einfach einen alternativen Weg auf. Der bessere Weg ist, struct unveränderlich zu machen und einen geeigneten Konstruktor bereitzustellen.

nawfal
quelle
2
Würde das nicht den Wert von Origin.Ywegwerfen? Bei einer Eigenschaft vom Typ Pointwürde ich denken, dass der idiomatische Weg, sich zu ändern, einfach Xwäre var temp=thing.Origin; temp.X = 23; thing.Origin = temp;. Der idiomatische Ansatz hat den Vorteil, dass die Mitglieder, die nicht geändert werden sollen, nicht erwähnt werden müssen. Diese Funktion ist nur möglich, weil sie Pointveränderbar ist. Ich bin verwirrt über die Philosophie, die besagt, dass man, weil der Compiler es nicht zulassen kann, Origin.X = 23;eine Struktur entwerfen sollte, die Code wie erfordert Origin.X = new Point(23, Origin.Y);. Letzteres scheint mir wirklich eklig zu sein.
Supercat
@supercat Dies ist das erste Mal, dass ich an deinen Punkt denke, macht sehr viel Sinn! Haben Sie eine alternative Muster- / Designidee, um dies anzugehen? Es wäre einfacher gewesen, wenn C # nicht standardmäßig den Standardkonstruktor für eine Struktur bereitgestellt hätte (in diesem Fall muss ich unbedingt beide Xund Yeinen bestimmten Konstruktor übergeben). Jetzt verliert es den Punkt, an dem man es tun kann Point p = new Point(). Ich weiß, warum es wirklich für eine Struktur erforderlich ist, also macht es keinen Sinn, darüber nachzudenken. Aber haben Sie eine coole Idee, nur eine Eigenschaft wie zu aktualisieren X?
Nawfal
Bei Strukturen, die eine Sammlung unabhängiger, aber verwandter Variablen (z. B. Koordinaten eines Punkts) enthalten, ist es meine Präferenz, dass die Struktur einfach alle ihre Mitglieder als öffentliche Felder verfügbar macht. Um ein Mitglied einer struct-Eigenschaft zu ändern, lesen Sie es einfach aus, ändern Sie das Element und schreiben Sie es zurück. Es wäre schön gewesen, wenn C # eine "einfache Plain-Old-Data-Struct" -Deklaration bereitgestellt hätte, die automatisch einen Konstruktor definiert, dessen Parameterliste mit der Feldliste übereinstimmt, aber die für C # Verantwortlichen veränderliche Strukturen verachten.
Supercat
@ Supercat Ich verstehe. Das inkonsistente Verhalten von Strukturen und Klassen ist verwirrend.
Nawfal
Die Verwirrung resultiert aus der meiner Meinung nach nicht hilfreichen Überzeugung, dass sich alles wie ein Klassenobjekt verhalten sollte. Während es nützlich ist, Werte vom Werttyp an Dinge zu übergeben, die Heap-Objektreferenzen erwarten, ist es nicht nützlich, so zu tun, als ob Variablen vom Werttyp Dinge enthalten, von denen sie abgeleitet sind Object. Sie tun es nicht. Jede Werttypdefinition definiert tatsächlich zwei Arten von Dingen: einen Speicherorttyp (der für Variablen, Array-Slots usw. verwendet wird) und einen Heap-Objekttyp, der manchmal als "Boxed" -Typ bezeichnet wird (wird verwendet, wenn ein Werttypwert verwendet wird) wird an einem Ort vom Referenztyp gespeichert).
Supercat
2

Abgesehen von der Debatte über die Vor- und Nachteile von Strukturen gegenüber Klassen neige ich dazu, das Ziel zu betrachten und das Problem aus dieser Perspektive zu betrachten.

Wenn Sie jedoch keinen Code hinter die get- und set-Methoden der Eigenschaft schreiben müssen (wie in Ihrem Beispiel), ist es dann nicht einfacher, die einfach Originals Feld der Klasse und nicht als Eigenschaft zu deklarieren ? Ich sollte denken, dies würde es Ihnen ermöglichen, Ihr Ziel zu erreichen.

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
Mitselplik
quelle
0

Das Problem besteht darin, dass Sie auf einen Wert im Stapel verweisen und der Wert nicht an die Eigenschaft orignal zurückgegeben wird, sodass Sie mit C # keinen Verweis auf einen Werttyp zurückgeben können. Ich denke, Sie können dies lösen, indem Sie die Origin-Eigenschaft entfernen und stattdessen eine öffentliche Datei verwenden. Ja, ich weiß, dass dies keine gute Lösung ist. Die andere Lösung besteht darin, den Punkt nicht zu verwenden und stattdessen Ihren eigenen Punkttyp als Objekt zu erstellen.

Fredrik Normén
quelle
Wenn das PointMitglied eines Referenztyps ist, befindet es sich nicht auf dem Stapel, sondern auf dem Heap im Speicher des enthaltenen Objekts.
Greg Beech
0

Ich denke, der Haken hier ist, dass Sie versuchen, die Unterwerte des Objekts in der Anweisung zuzuweisen, anstatt das Objekt selbst zuzuweisen. In diesem Fall müssen Sie das gesamte Point-Objekt zuweisen, da der Eigenschaftstyp Point ist.

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

Hoffe ich habe dort Sinn gemacht

MSIL
quelle
2
Der wichtige Punkt ist, dass Point eine Struktur (Wertetyp) ist. Wenn es eine Klasse (ein Objekt) wäre, hätte der ursprüngliche Code funktioniert.
Hans
@HansKesting: Wenn Pointein veränderbarer Klassentyp wäre, hätte der ursprüngliche Code ein Feld oder eine Eigenschaft Xin dem von der Eigenschaft zurückgegebenen Objekt festgelegt Origin. Ich sehe keinen Grund zu der Annahme, dass dies die gewünschte Wirkung auf das Objekt haben würde, das die OriginEigenschaft enthält. Einige Framework-Klassen verfügen über Eigenschaften, die ihren Status in neue veränderbare Klasseninstanzen kopieren und diese zurückgeben. Ein solches Design hat den Vorteil, dass Code thing1.Origin = thing2.Origin;den Status des Ursprungs des Objekts so einstellen kann, dass er mit dem eines anderen übereinstimmt, aber nicht vor Code wie warnen kann thing1.Origin.X += 4;.
Supercat
0

Entfernen Sie einfach die Eigenschaft "get set" wie folgt, und dann funktioniert alles wie immer.

Bei primitiven Typen instread verwenden Sie 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]

    }
}      
Roberto Mutti
quelle
0

Ich denke , eine Menge Leute hier sind verwirrt zu werden , dieses besondere Problem bezieht sich auf das Verständnis , dass Werttyp Eigenschaften eine Kopie des Werttyp zurückgeben (wie bei Methoden und Indexer) und Werttyp Felder werden direkt zugegriffen . Der folgende Code macht genau das, was Sie erreichen möchten, indem Sie direkt auf das Hintergrundfeld der Eigenschaft zugreifen (Hinweis: Das Ausdrücken einer Eigenschaft in ihrer ausführlichen Form mit einem Hintergrundfeld entspricht einer Auto-Eigenschaft, hat jedoch den Vorteil, dass dies in unserem Code möglich ist direkt auf das Hintergrundfeld zugreifen):

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

Der Fehler, den Sie erhalten, ist eine indirekte Folge des Nichtverständnisses, dass eine Eigenschaft eine Kopie eines Werttyps zurückgibt. Wenn Sie eine Kopie eines Werttyps zurückgeben und diese keiner lokalen Variablen zuweisen, können alle Änderungen, die Sie an dieser Kopie vornehmen, niemals gelesen werden. Der Compiler gibt dies daher als Fehler aus, da dies nicht beabsichtigt sein kann. Wenn wir die Kopie einer lokalen Variablen zuweisen, können wir den Wert von X ändern. Er wird jedoch nur für die lokale Kopie geändert, wodurch der Fehler bei der Kompilierung behoben wird, die gewünschte Origin-Eigenschaft jedoch nicht geändert wird. Der folgende Code veranschaulicht dies, da der Kompilierungsfehler behoben ist, die Debug-Bestätigung jedoch fehlschlägt:

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
    }
}
Matt
quelle