代码之家  ›  专栏  ›  技术社区  ›  Travis Gockel

C#:为什么只读结构上的突变不会中断?

  •  13
  • Travis Gockel  · 技术社区  · 14 年前

    在C#,如果你有 struct 像这样:

    struct Counter
    {
        private int _count;
    
        public int Value
        {
            get { return _count; }
        }
    
        public int Increment()
        {
            return ++_count;
        }
    }
    

    static readonly Counter counter = new Counter();
    
    static void Main()
    {
        // print the new value from the increment function
        Console.WriteLine(counter.Increment());
        // print off the value stored in the item
        Console.WriteLine(counter.Value);
    }
    

    1
    0
    

    这似乎是完全错误的。我希望输出是两个1(如果 Counter class 或者如果 struct Counter : ICounter counter 是一个 ICounter )或者是编译错误。我意识到在编译时检测这种情况是一件相当困难的事情,但这种行为似乎违反了逻辑。

    这种行为有没有超出执行难度的原因?

    2 回复  |  直到 5 年前
        1
  •  8
  •   Community CDub    8 年前

    structs 是值类型,因此具有值类型语义。这意味着每次访问结构时,基本上都会使用结构值的副本。

    struct 但只是一个临时的副本。

    Why are mutable structs evil

        2
  •  3
  •   supercat    12 年前

    在.net中,结构实例方法在语义上等价于带有额外 ref 结构类型的参数。因此,鉴于声明:

    struct Blah { 
       public int value;
       public void Add(int Amount) { value += Amount; }
       public static void Add(ref Blah it; int Amount; it.value += Amount;}
    }
    

    someBlah.Add(5);
    Blah.Add(ref someBlah, 5);
    

    在语义上是等价的,除了一个区别:只有在 someBlah 是可变存储位置(变量、字段等),如果是只读存储位置或临时值(读取属性等的结果),则不是。

    这就给.net语言的设计者带来了一个问题:不允许在只读结构上使用任何成员函数会很烦人,但他们不想允许成员函数写入只读变量。他们决定“punt”,这样在只读结构上调用一个实例方法将生成该结构的一个副本,调用该结构上的函数,然后丢弃它。这样做的效果是减慢对不编写底层结构的实例方法的调用,并使之成为这样,以便尝试使用在只读结构上更新底层结构的方法将产生错误 不同的破碎语义 如果直接将其传递给struct,将实现什么。请注意,在没有副本就不正确的情况下,副本占用的额外时间几乎永远不会产生正确的语义。

    我在.net中的一个主要问题是(至少在4.0版,可能在4.5版)仍然没有一个属性,结构成员函数可以通过它来指示是否修改 this 所谓的“不变”结构是谎言 struct1 = struct2 通过复制所有公共 而且是私人的 struct2中的字段,并且struct的类型定义不能防止这种情况(除了没有任何字段)它不能防止结构成员的意外突变。此外,由于线程问题,结构在其字段之间执行任何类型不变关系的能力非常有限。一般来说,结构允许任意的字段访问,明确任何接收结构的代码都必须检查其字段是否满足所有必需的条件,而不是试图阻止不满足条件的结构的形成。