错误:“无法修改返回值” c#


155

我正在使用自动实现的属性。我猜想解决以下问题的最快方法是声明我自己的后备变量?

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

错误消息:无法修改“表达式”的返回值,因为它不是变量

试图修改作为中间表达式结果的值类型。因为该值不是持久性的,所以该值将保持不变。

若要解决此错误,请将表达式的结果存储在中间值中,或对中间表达式使用引用类型。


13
这是为什么可变值类型不是一个好主意的又一个例证。如果可以避免更改值类型,请这样做。
埃里克·利珀特

采取以下代码(从我为某个EL撰写的AStar实现中所做的努力:-),这不可避免地更改了值类型:class Path <T>:IEnumerable <T>其中T:INode,new(){。 ..} public HexNode(int x,int y):this(new Point(x,y)){} Path <T> path = new Path <T>(new T(x,y)); //错误//错误修复Path <T> path = new Path <T>(new T()); path.LastStep.Centre = new Point(x,y);
汤姆·威尔逊

Answers:


198

这是因为Point是值类型(struct)。

因此,当您访问Origin属性时,您将访问该类所拥有的值的副本,而不是使用引用类型(class)时的值本身,因此,如果您X在其上设置属性,则需要设置副本上的属性,然后将其丢弃,保留原始值不变。这可能不是您想要的,这就是编译器向您发出警告的原因。

如果只想更改X值,则需要执行以下操作:

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

2
@Paul:您有能力将结构更改为类吗?
道格2014年

1
这有点让人不知所措,因为即时消息传递给属性设置程序有副作用(该结构充当后备引用类型的视图)
Alexander-Reinstate Monica

另一个解决方案是将您的结构简单地变成一个类。与C ++中的类和结构仅在默认成员访问权限(分别为私有和公共)上有所不同的情况不同,C#中的结构和类还有更多区别。这里的一些信息:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/...
Artorias2718

9

使用后备变量将无济于事。Point类型是值类型。

您需要将整个Point值分配给Origin属性:-

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

问题在于,当您访问Origin属性时,由返回的get是Origin属性自动创建字段中Point结构的副本。因此,您对该副本的X字段所做的修改不会影响基础字段。编译器检测到此错误,并给您错误,因为此操作完全无用。

即使您使用了自己的后备变量,您get也会看起来像:

get { return myOrigin; }

您仍然会返回Point结构的副本,并且会遇到相同的错误。

嗯...仔细阅读了您的问题,也许您实际上的意思是直接从您的班级内部修改后备变量:-

myOrigin.X = 10;

是的,这就是您所需要的。


6

到目前为止,您已经知道错误的根源是什么。如果不存在带有重载来获取您的属性的构造函数(在本例中为X),则可以使用对象初始化程序(它将完成所有幕后工作)。并不是说您不必使结构不可变,而只需提供其他信息即可:

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.

这是可能的,因为在幕后会发生这种情况:

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

这看起来很奇怪,不建议这样做。只是列出另一种方法。更好的方法是使struct不可变并提供适当的构造函数。


2
那会不会浪费掉价值Origin.Y?给定类型的属性Point,我认为习惯的改变方式X将会是var temp=thing.Origin; temp.X = 23; thing.Origin = temp;。惯用方法的优点是不必提及不想修改的成员,该功能仅Point是可变的,因此是可能的。我对这样的哲学感到困惑,因为哲学不允许编译器Origin.X = 23;设计一种结构来要求像这样的代码Origin.X = new Point(23, Origin.Y);。后者对我来说似乎真的很邪恶。
2013年

@supercat这是我第一次想到您的观点,很有意义!您是否有其他模式/设计想法来解决这个问题?它本来就容易了C#不是默认一个结构提供的默认构造函数(在这种情况下,我公司严格必须同时通过XY特定的构造函数)。现在它失去了一个人可以做的意义Point p = new Point()。我知道为什么它真正需要一个结构,所以思考它毫无意义。但是,您是否有一个不错的主意来更新像这样的一个属性X
nawfal

对于封装了独立但相关变量(例如点的坐标)的集合的结构,我的首选是简单地使该结构将其所有成员公开为公共字段。要修改struct属性的一个成员,只需将其读出,修改该成员并写回即可。如果C#提供了一个“简单的Plain-Old-Data-Struct”声明,它会自动定义一个其参数列表与字段列表匹配的构造函数,但是负责C#的人员却鄙视可变结构,那将是很好的。
超级猫

@supercat我明白了。结构和类的不一致行为令人困惑。
nawfal 2013年

混淆是由于恕我直言无助的信念所致,即一切行为都应像类对象一样。虽然有一种方法可以将值类型的值传递给需要堆对象引用的对象,但假装值类型的变量保留从中派生的对象也没有用Object。他们没有。每个值类型定义实际上定义两种类型的东西:存储位置类型(用于变量,数组插槽等)和堆对象类型,有时称为“盒装”类型(在值类型值时使用)存储到参考类型的位置)。
2013年

2

除了辩论结构与类的优缺点之外,我倾向于从这个角度看待目标并解决问题。

话虽这么说,如果您不需要在属性get和set方法后面编写代码(如您的示例),那么简单地将Originas 声明为类的字段而不是属性会更容易吗?我认为这将使您实现目标。

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

问题是您指向堆栈上的一个值,并且该值将不会被改写回orignal属性,因此C#不允许您返回对值类型的引用。我认为您可以通过删除Origin属性来解决此问题,而改用公共文件,是的,我知道这不是一个很好的解决方案。另一种解决方案是不使用Point,而是创建自己的Point类型作为对象。


如果Point引用类型的成员,则它将不在堆栈中,而将在包含对象的内存中的堆中。
格雷格·比奇

0

我想这里要抓住的是,您正在尝试在语句中分配对象的子值,而不是分配对象本身。在这种情况下,由于属性类型为Point,因此需要分配整个Point对象。

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

希望我在那里有意义


2
重要的一点是Point是一个结构(值类型)。如果它是一个类(对象),那么原始代码将起作用。
汉斯·基辛

@HansKesting:如果Point是可变的类类型,则原始代码将在property X返回的对象中设置字段或属性Origin。我没有理由相信这会对包含该Origin属性的对象产生预期的效果。某些Framework类具有将其状态复制到新的可变类实例并返回它们的属性。这样的设计的优点是允许类似代码thing1.Origin = thing2.Origin;的对象设置对象的原始状态以匹配另一个对象的原始状态,但是它不会警告类似的代码thing1.Origin.X += 4;
2013年

0

只需按如下所示删除属性“ get set”,然后一切都会正常进行。

如果原始类型为instread,请使用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

我认为很多人对此感到困惑,这个特定问题与理解值类型属性返回值类型的副本(与方法和索引器一样),并且可以直接访问值类型字段有关。以下代码完全通过直接访问属性的后备字段来实现您想要实现的目的(请注意:用后备字段表示其冗长形式的属性等效于auto属性,但是其优点在于,在我们的代码中,我们可以直接访问后备字段):

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

您收到的错误是由于不了解属性返回值类型的副本的间接结果。如果返回的是值类型的副本,并且未将其分配给局部变量,则您对该副本所做的任何更改都将无法读取,因此编译器将其视为错误,因为这不是故意的。如果确实将副本分配给本地变量,则可以更改X的值,但是只能在本地副本上更改它的值,这样可以解决编译时错误,但无法达到修改Origin属性的预期效果。以下代码说明了这一点,因为编译错误已消失,但调试断言将失败:

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
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.