为什么struct可以改变自己的领域?

时间:2011-01-18 04:26:11

标签: c# struct

考虑Foo结构如下:

struct Foo
{
  public float X;
  public float Y;

  public Foo(float x, float y)
  {
    this.X = x;
    this.Y = y;
  }

  public void Change(float x)
  {
    this.X = x;
  }
}

我理解修改构造函数中的字段,这对我和我对结构体的理解是合乎逻辑的,类似于数字的 immutable 类型。

但是,因为可以't 执行:

Foo bar = new Foo(1, 2);
bar.X = 5;

为什么可以使用:

Foo bar = new Foo(1, 2);
bar.Change(5);

编辑:如果结构是可变的,那么为什么在列表中或从属性返回时它们不能被修改?

Cannot modify expression because it is not a variable

5 个答案:

答案 0 :(得分:11)

  

因为一个人做不到

Foo bar = new Foo(1, 2); 
bar.X = 5; 
  

为什么可以使用:

Foo bar = new Foo(1, 2); 
bar.Change(5); 

您的原始问题实际上无法回答,因为它基于完全错误的假设。两个代码示例都是完全合法的,因此关于为什么一个是非法的问题是荒谬的。让我们继续你的后续问题:

  

如果结构是可变的,那么为什么在列表中或从属性返回时它们不能被修改?

因为变量是可变的,值是不可变的。

这就是为什么他们被称为“变量”,毕竟因为他们可以改变

当您说“bar.X = 5”时,“bar”是本地变量。变量可以改变。

当您说“bar.Change(5)”时,“bar”是本地变量。变量可以改变。

当你说“myArray [123] .X = 5”时,“myArray [123]”是数组元素,数组元素是变量。变量可以改变。

当您说“myDictionary [123] .X = 5”时,“myDictionary [123]”不是变量从字典返回,而不是对存储位置的引用。由于这是一个值,而不是一个变量,因此没有任何东西可以改变,所以编译器不允许它改变。

一个细微之处在于,当您尝试更改字段时,接收方必须是变量。如果它不是变量,那就毫无意义;你显然是在试图改变一个变量而且没有什么可以变异的。当你调用一个方法时,接收者必须是一个变量,但如果你有一个值呢?该方法可能不会尝试改变任何东西,因此应该允许成功。如果方法的接收者在结构上调用不是变量,编译器实际上做了什么,那么它创建一个新的临时局部变量并使用该变量调用该方法。所以如果你说:“myDictionary [123] .Change(5);”这与说

相同
var temp = myDictionary[123];
temp.Change(5);

这里“temp”是一个变量,允许变异方法改变临时副本。

现在清楚了吗?这里的关键点是变量可以改变

答案 1 :(得分:8)

你做了一个错误的错误假设。

.NET结构是可变的。您绝对可以执行bar.X = 5;

你应该将结构设计为不可变的,但是根据你提供的代码,它们是可变的。

看一下这个问题,了解可变结构可能导致麻烦的地方。 Immutability of structs

答案 2 :(得分:2)

通常,所有C#结构都不是不可变的,即使是只读的结构。因此,您根本无法将结构设计为不可变的。

所有结构都是可变的,就像在C ++中一样:)

不可变性意味着数据结构在语言级别上是不可变的,而C#则不然。 我将向您展示如何使用合法的C#语法打破不变性规则,请注意 NotReallyImmutableFoo.X 被声明为 readonly 字段。

干杯;)

namespace test
{
    public unsafe struct MutableFoo
    {
        public int Id;
        public float X;
        public MutableFoo(int id, float x) { Id = id; X = x; }
        public void Change(float x)
        {
            unsafe
            {
                fixed (MutableFoo* self = &(this))
                {
                    MutabilityHelper.Rewrite(self, x);
                }
            }
        }
    }

    public struct NotReallyImmutableFoo
    {
        public long Id;
        public readonly float X;
        public NotReallyImmutableFoo(long id, float x) { Id = id; X = x; }
        public void Change(float x)
        {
            unsafe
            {
                fixed (NotReallyImmutableFoo* self = &(this))
                {
                    MutabilityHelper.Rewrite(self, x);
                }
            }
        }
    }

    // this calls breaks up the immutability rule, because we are modifying structures itself
    public static class MutabilityHelper
    {
        struct MutableFooPrototype
        {
            int Id;
            float X;
            public void Rewrite(float value)
            {
                X = value;
            }
        }
        struct NotReallyImmutableFooPrototype
        {
            long Id;
            float X;
            public void Rewrite(float value)
            {
                X = value;
            }
        }
        public static unsafe void Rewrite(NotReallyImmutableFoo* obj, float value)
        {
            NotReallyImmutableFooPrototype* p_obj = (NotReallyImmutableFooPrototype*)(*(&obj));
            p_obj->Rewrite(value);
        }
        public static unsafe void Rewrite(MutableFoo* obj, float value)
        {
            MutableFooPrototype* p_obj = (MutableFooPrototype*)(*(&obj));
            p_obj->Rewrite(value);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MutableFoo foo = new MutableFoo(0, 2);
            foo.X = 3; // X is writeable
            foo.Change(5); // write X using pointer prototyping

            NotReallyImmutableFoo nrifoo = new NotReallyImmutableFoo(0, 2);
            // error CS0191
            //nrifoo.X = 3; // X is not writeable
            nrifoo.Change(3); // anyway, write X using pointer prototyping
        }
    }
}

答案 3 :(得分:0)

在最底层找到我的答案: http://www.albahari.com/valuevsreftypes.aspx

但是,我仍然不明白为什么编译器允许在这种情况下使用可变结构。

答案 4 :(得分:0)

.net中的结构将分段可变性与赋值分配浅层语义以及通过赋值或引用传递的能力相结合。然而,.net中没有约定,期望通过引用公开类,也不会有任何.net语言编译器提供这样做的方便方法。通过识别以下内容,语言可以提供具有某些限制的特征:

  somePoint.X = 5;

可以写成:

void SetXToFive(ref Point it) {it.X = 5;}
...
  SetXToFive(ref somePoint);

允许操作Point的代码(通过将其X字段设置为5)来自有权访问它的代码。如果一个具有Point类型属性的对象然后公开一个接受上述方法的委托的例程,那么想要将该属性的字段X设置为5的代码可以将该例程传递给{ {1}},然后例程可以使用任何存储位置调用SetXToFive

请注意,与简单地公开对要操作的事物的引用相比,这种方法的一个优点是Point的所有者会知道操作它的代码何时完成。如果没有一些编译器晚餐,这种方法通常会比一个好处更令人讨厌,但是通过编译器支持,语义可以比通过任何其他方式更清晰。