无法修改返回值错误c#

P a*_*u l 136 c# variables struct immutability

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

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612
Run Code Online (Sandbox Code Playgroud)

错误消息:无法修改'expression'的返回值,因为它不是变量

尝试修改作为中间表达式结果的值类型.由于该值未持久存在,因此该值将保持不变.

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

Gre*_*ech 177

这是因为Point是一个值类型(struct).

因此,当您访问该Origin属性时,您正在访问该类所持有的值的副本,而不是像引用类型(class)那样访问值本身,因此如果您X在其上设置属性,那么您正在设置副本上的属性然后丢弃它,保持原始值不变.这可能不是你想要的,这就是编译器警告你的原因.

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

Origin = new Point(10, Origin.Y);
Run Code Online (Sandbox Code Playgroud)

  • @Paul:您有能力将结构更改为类吗? (2认同)
  • 这有点令人失望,因为我分配的属性设置器有一个副作用(该结构充当后备引用类型的视图) (2认同)

Ant*_*nes 8

使用支持变量无济于事.Point类型是数值类型.

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

Origin = new Point(10, Origin.Y);
Run Code Online (Sandbox Code Playgroud)

问题是当您访问Origin属性时,它返回的get是Origin属性自动创建字段中Point结构的副本.因此,您修改X字段此副本不会影响基础字段.编译器检测到这一点并给出错误,因为此操作完全无用.

即使您使用自己的支持变量,您get也会看起来像: -

get { return myOrigin; }
Run Code Online (Sandbox Code Playgroud)

你仍然会返回Point结构的副本,你会得到同样的错误.

嗯......更仔细地阅读你的问题或许你实际上是指直接从你的班级修改支持变量: -

myOrigin.X = 10;
Run Code Online (Sandbox Code Playgroud)

是的,这将是你需要的.


naw*_*fal 7

到现在为止,您已经知道错误的根源是什么。如果构造函数不存在重载来获取您的属性(在本例中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.
Run Code Online (Sandbox Code Playgroud)

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

Point tmp = new Point();
tmp.X = 23;
tmp.Y = Origin.Y;
c.Origin = tmp;
Run Code Online (Sandbox Code Playgroud)

这看起来很奇怪,完全不推荐。只是列出另一种方式。更好的方法是使 struct 不可变并提供适当的构造函数。

  • 这不会破坏“Origin.Y”的价值吗?给定一个`Point`类型的属性,我认为改变`X`的惯用方法是`var temp=thing.Origin; 温度.X = 23; thing.Origin = temp;`。惯用方法的优点是它不必提及它不想修改的成员,这是唯一可能的特性,因为`Point` 是可变的。我对这样一种哲学感到困惑,因为编译器不能允许 `Origin.X = 23;` 应该设计一个结构来要求像 `Origin.X = new Point(23, Origin.Y);` 这样的代码. 后者对我来说似乎很恶心。 (2认同)

Mat*_*att 5

我认为很多人在这里感到困惑,这个特定问题与理解值类型属性返回值类型的副本(与方法和索引器一样)以及直接访问值类型字段有关。以下代码正是通过直接访问属性的支持字段来实现您想要实现的目标(注意:使用支持字段以其详细形式表达属性相当于自动属性,但优点是在我们的代码中我们可以直接访问支持字段):

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

您收到的错误是不理解属性返回值类型的副本的间接结果。如果返回值类型的副本并且没有将其分配给局部变量,则永远无法读取对该副本所做的任何更改,因此编译器会将此作为错误引发,因为这不可能是故意的。如果我们确实将副本分配给局部变量,那么我们可以更改 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
    }
}
Run Code Online (Sandbox Code Playgroud)