访问和更改结构作为属性与字段

Nem*_*ein 1 c# struct

好吧,我将开始我的问题,说我理解可变结构背后的邪恶,但我正在使用SFML.net并使用大量的Vector2f和这样的结构.

我不明白为什么我可以拥有,并且更改类中的字段的值,并且在同一个类中不能对属性执行相同的操作.

看看这段代码:

using System;

namespace Test
{
    public struct TestStruct
    {
        public string Value;
    }

    class Program
    {
        TestStruct structA;
        TestStruct structB { get; set; }

        static void Main(string[] args)
        {
            Program program = new Program();

            // This Works
            program.structA.Value = "Test A";

            // This fails with the following error:
            // Cannot modify the return value of 'Test.Program.structB'
            // because it is not a variable
            //program.structB.Value = "Test B"; 

            TestStruct copy = program.structB;
            copy.Value = "Test B";

            Console.WriteLine(program.structA.Value); // "Test A"
            Console.WriteLine(program.structB.Value); // Empty, as expected
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:我将构建自己的类来覆盖相同的功能并保持可变性,但我看不出技术原因,为什么我可以做一个而不能做其他的.

And*_*bel 6

访问字段时,您正在访问实际的结构.当您通过属性访问它时,您将调用一个方法来返回存储在属性中的内容.对于结构,它是一个值类型,您将获得结构的副本.显然,副本不是变量,不能更改.

C#语言规范5.0的"1.7结构"部分说:

对于类,两个变量可以引用同一个对象,因此对一个变量的操作可能会影响另一个变量引用的对象.对于结构体,每个变量都有自己的数据副本,并且一个变量不可能影响另一个变量.

这说明您将收到结构的副本,而无法修改原始结构.但是,它没有描述为什么不允许这样做.

规范的"11.3.3"部分:

当结构的属性或索引器是赋值的目标时,与属性或索引器访问关联的实例表达式必须归类为变量.如果实例表达式被分类为值,则发生编译时错误.这在§7.17.1中有更详细的描述.

所以从get访问器返回的"东西"是一个值而不是一个变量.这解释了错误消息中的措辞.

该规范还包含7.17.1节中与您的代码几乎相同的示例:

鉴于声明:

struct Point
{
    int x, y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int X {
        get { return x; }
        set { x = value; }
    }
    public int Y {
        get { return y; }
        set { y = value; }
    }
}
struct Rectangle
{
    Point a, b;
    public Rectangle(Point a, Point b) {
        this.a = a;
        this.b = b;
    }
    public Point A {
        get { return a; }
        set { a = value; }
    }
    public Point B {
        get { return b; }
        set { b = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

在示例中

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;
Run Code Online (Sandbox Code Playgroud)

允许分配pX,pY,rA和rB,因为p和r是变量.但是,在示例中

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;
Run Code Online (Sandbox Code Playgroud)

分配都是无效的,因为rA和rB不是变量.


sup*_*cat 5

尽管属性看起来像变量,但每个属性实际上都是 get 方法和/或 set 方法的组合。通常,属性 get 方法将返回某个变量或数组槽中内容的副本,而 put 方法将其参数复制到该变量或数组槽中。如果想要做类似someVariable = someObject.someProeprty;or 的someobject.someProperty = someVariable;事情,那么这些语句最终分别作为var temp=someObject.somePropertyBackingField; someVariable=temp;and执行都没有关系var temp=someVariable; someObject.somePropertyBackingField=temp;。另一方面,有些操作可以用字段来完成,但不能用属性来完成。

如果一个对象George公开了一个名为 的字段Field1,那么代码可以George.Field作为refout参数传递给另一个方法。此外,如果 的类型Field1是具有公开字段的值类型,则尝试访问这些字段将访问存储在George. 如果Field1有公开的属性或方法,则访问这些属性或方法将导致George.Field1将其传递给这些方法,就好像它是一个ref参数一样。

如果George公开了一个名为 的属性Property1,那么访问Property1不是赋值运算符左侧的将调用“get”方法并将其结果存储在一个临时变量中。尝试读取 的Property1字段将从临时变量中读取该字段。尝试调用属性 getter 或方法Property1会将该临时变量作为ref参数传递给该方法,然后在该方法返回后将其丢弃。在方法或属性 getter 或方法中,this将引用临时变量,并且该方法所做的任何更改this都将被丢弃。

因为写入临时变量的字段没有意义,所以禁止尝试写入属性的字段。此外,当前版本的 C# 编译器会猜测属性设置器可能会修改this,因此即使它们实际上不会修改底层结构,也会禁止使用任何属性设置器[例如,原因ArraySegment包括索引get方法而不是索引方法set方法是,如果人们试图说例如thing.theArraySegment[3] = 4;编译器会认为人们试图修改由theArraySegment属性返回的结构,而不是修改其引用封装在其中的数组]。如果可以指定特定的结构方法将修改,那将非常有用this 并且不应在结构属性上调用,但目前尚不存在相关机制。

如果要写入包含在属性中的字段,最好的模式通常是:

var temp = myThing.myProperty; // Assume `temp` is a coordinate-point structure
temp.X += 5;
myThing.myProperty = temp;
Run Code Online (Sandbox Code Playgroud)

如果类型myProperty旨在封装一组固定的相关但独立的值(例如点的坐标),则最好将这些变量作为字段公开。尽管有些人似乎更喜欢设计结构以要求结构如下:

var temp = myThing.myProperty; // Assume `temp` is some kind of XNA Point structure
myThing.myProperty = new CoordinatePoint(temp.X+5, temp.Y);
Run Code Online (Sandbox Code Playgroud)

我认为这样的代码与以前的风格相比可读性更差、效率更低、更容易出错。除此之外,如果CoordinatePoint碰巧暴露了一个带有参数 X、Y、Z 的构造函数以及一个接受参数 X、Y 并假定 Z 为零的构造函数,则像第二种形式的代码会将 Z 清零,而没有任何迹象表明它是这样做(有意或无意)。相比之下, ifX是一个暴露的字段,第一种形式只会修改X.

在某些情况下,类通过将内部字段或数组槽作为ref参数传递给用户定义的例程的方法公开内部字段或数组槽可能会有所帮助,例如List<T>-like 类可能公开:

delegate void ActByRef<T1>(ref T1 p1);
delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);

void ActOnItem(int index, ActByRef<T> proc)
{
  proc(ref BackingArray[index]);
}
void ActOnItem<PT>(int index, ActByRef<T,PT> proc, ref PT extraParam)
{
  proc(ref BackingArray[index], ref extraParam);
}
Run Code Online (Sandbox Code Playgroud)

具有 aFancyList<CoordinatePoint>并想将一些局部变量添加dx到 iit 中项目 5 的字段 X 的代码可以执行以下操作:

myList.ActOnItem(5, (ref Point pt, ref int ddx) => pt.X += ddx, ref dx);
Run Code Online (Sandbox Code Playgroud)

请注意,这种方法将允许就地修改列表中的数据,甚至允许使用诸如Interlocked.CompareExchange)之类的方法。不幸的是,派生自的类型没有可能List<T>支持这种方法的机制,也没有机制可以将支持这种方法的类型传递给需要List<T>.