我正在使用自动实现的属性。我猜想解决以下问题的最快方法是声明我自己的后备变量?
public Point Origin { get; set; }
Origin.X = 10; // fails with CS1612
错误消息:无法修改“表达式”的返回值,因为它不是变量
试图修改作为中间表达式结果的值类型。因为该值不是持久性的,所以该值将保持不变。
若要解决此错误,请将表达式的结果存储在中间值中,或对中间表达式使用引用类型。
我正在使用自动实现的属性。我猜想解决以下问题的最快方法是声明我自己的后备变量?
public Point Origin { get; set; }
Origin.X = 10; // fails with CS1612
错误消息:无法修改“表达式”的返回值,因为它不是变量
试图修改作为中间表达式结果的值类型。因为该值不是持久性的,所以该值将保持不变。
若要解决此错误,请将表达式的结果存储在中间值中,或对中间表达式使用引用类型。
Answers:
这是因为Point
是值类型(struct
)。
因此,当您访问Origin
属性时,您将访问该类所拥有的值的副本,而不是使用引用类型(class
)时的值本身,因此,如果您X
在其上设置属性,则需要设置副本上的属性,然后将其丢弃,保留原始值不变。这可能不是您想要的,这就是编译器向您发出警告的原因。
如果只想更改X
值,则需要执行以下操作:
Origin = new Point(10, Origin.Y);
使用后备变量将无济于事。 该Point
类型是值类型。
您需要将整个Point值分配给Origin属性:-
Origin = new Point(10, Origin.Y);
问题在于,当您访问Origin属性时,由返回的get
是Origin属性自动创建字段中Point结构的副本。因此,您对该副本的X字段所做的修改不会影响基础字段。编译器检测到此错误,并给您错误,因为此操作完全无用。
即使您使用了自己的后备变量,您get
也会看起来像:
get { return myOrigin; }
您仍然会返回Point结构的副本,并且会遇到相同的错误。
嗯...仔细阅读了您的问题,也许您实际上的意思是直接从您的班级内部修改后备变量:-
myOrigin.X = 10;
是的,这就是您所需要的。
到目前为止,您已经知道错误的根源是什么。如果不存在带有重载来获取您的属性的构造函数(在本例中为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不可变并提供适当的构造函数。
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);
。后者对我来说似乎真的很邪恶。
X
与Y
特定的构造函数)。现在它失去了一个人可以做的意义Point p = new Point()
。我知道为什么它真正需要一个结构,所以思考它毫无意义。但是,您是否有一个不错的主意来更新像这样的一个属性X
?
Object
。他们没有。每个值类型定义实际上定义两种类型的东西:存储位置类型(用于变量,数组插槽等)和堆对象类型,有时称为“盒装”类型(在值类型值时使用)存储到参考类型的位置)。
除了辩论结构与类的优缺点之外,我倾向于从这个角度看待目标并解决问题。
话虽这么说,如果您不需要在属性get和set方法后面编写代码(如您的示例),那么简单地将Origin
as 声明为类的字段而不是属性会更容易吗?我认为这将使您实现目标。
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
问题是您指向堆栈上的一个值,并且该值将不会被改写回orignal属性,因此C#不允许您返回对值类型的引用。我认为您可以通过删除Origin属性来解决此问题,而改用公共文件,是的,我知道这不是一个很好的解决方案。另一种解决方案是不使用Point,而是创建自己的Point类型作为对象。
Point
引用类型的成员,则它将不在堆栈中,而将在包含对象的内存中的堆中。
我想这里要抓住的是,您正在尝试在语句中分配对象的子值,而不是分配对象本身。在这种情况下,由于属性类型为Point,因此需要分配整个Point对象。
Point newOrigin = new Point(10, 10);
Origin = newOrigin;
希望我在那里有意义
Point
是可变的类类型,则原始代码将在property X
返回的对象中设置字段或属性Origin
。我没有理由相信这会对包含该Origin
属性的对象产生预期的效果。某些Framework类具有将其状态复制到新的可变类实例并返回它们的属性。这样的设计的优点是允许类似代码thing1.Origin = thing2.Origin;
的对象设置对象的原始状态以匹配另一个对象的原始状态,但是它不会警告类似的代码thing1.Origin.X += 4;
。
只需按如下所示删除属性“ 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]
}
}
我认为很多人对此感到困惑,这个特定问题与理解值类型属性返回值类型的副本(与方法和索引器一样),并且可以直接访问值类型字段有关。以下代码完全通过直接访问属性的后备字段来实现您想要实现的目的(请注意:用后备字段表示其冗长形式的属性等效于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
}
}