简体   繁体   中英

Why can I do this to an immutable `struct`?

Recently I had to serialize a class that contained complex (immutable) structures. I kept failing, until I came up with this (see ReadXml() ).

Consider the following code:

[ImmutableObject(true)]    
public struct Point : IXmlSerializable
{
    readonly int x, y;

    public Point(int x, int y)
    {
        this.x=x;
        this.y=y;
    }
    public Point(Point other)
    {
        this=other;
    }
    public int X { get { return x; } }
    public int Y { get { return y; } }

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }
    public void ReadXml(System.Xml.XmlReader reader)
    {
        // Immutable, right?
        int new_x =0, new_y=0;
        int.TryParse(reader.GetAttribute("X"), out new_x);
        int.TryParse(reader.GetAttribute("Y"), out new_y);
        // But I can change the contents by assigning to 'this'
        this=new Point(new_x, new_y);
    }
    public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteAttributeString("X", X.ToString());
        writer.WriteAttributeString("Y", Y.ToString());
    }
}

public class Foo
{
    Point from, to;
    public Foo() { }
    public Foo(Point from, Point to)
    {
        this.from=from;
        this.to=to;
    }
    public Point From { get { return from; } set { from=value; } }
    public Point To { get { return to; } set { to=value; } }
}

Which reads in the following xml file correctly.

<?xml version="1.0" encoding="utf-8"?>
<Foo>
  <From X="100" Y="30" />
  <To X="45" Y="75" />
</Foo>

My question is how is this=new Point(new_x, new_y); work when the contents are immutable ( readonly ) keyword? What is stopping me from adding members like

    public void Reset()
    {
        this=new Point(0, 0);
    }
    public void Add(Point other)
    {
        this=new Point(x+other.x, y+other.y);
    }

which alter the contents of my structure?

    {
        Point foo=new Point(10, 15);
        // foo.X=10, foo.Y=15
        Point bar=new Point(foo);   // Clone
        // bar.X=10, bar.Y=15

        foo.Reset();
        // foo.X=0, foo.Y=0

        bar.Add(bar);
        // bar.X=20, bar.Y=30
    }

I am glad this functionality exist because it allows me to read/write immutable structures with Xml files, it is just very surprising that it works.

Nothing is stopping you from writing Reset . In-fact, it will work without an issue.

It's possible with the readonly keyword, because you're actually creating a new struct, you're not modifying the original one.

Here's the code and generated IL:

public void Reset()
{
    this = new Point(0,0);
}

IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  ldc.i4.0    
IL_0004:  newobj      UserQuery+Point..ctor
IL_0009:  stobj       UserQuery.Point
IL_000E:  ret

Note the newobj call.

As Eric Lippert puts it: Basically, “readonly” fields in a struct are the moral equivalent of the struct author writing a cheque without having the funds to back it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM