A Bug with Structs and readonly
Today I ran into a really curious bug in C#. It’s entirely possible this isn’t a bug but intended behavior, but it certainly looks unusual.
In short: a readonly variable can only be changed inside the class constructor, and even if the constructor calls a method that modifies it, those changes get thrown away.
public struct TestStruct { private int m_count; private int m_value; public int Count { get { return m_count; } } public int Value { get { return m_value; } } public TestStruct(int value) { m_value = value; m_count = 0; } public void Increment() { m_count++; } } public class TestClass { private TestStruct m_struct; public int Value { get { return m_struct.Value; } } public int Count { get { return m_struct.Count; } } public TestClass(int value) { m_struct = new TestStruct(value); for (int i = 0; i < value; i++) Increment(); } private void Increment() { m_struct.Increment(); } }
If you create an instance of TestClass with some number, both Value and Count will end up equal to that number. Perfectly normal, logical behavior.
The picture changes if we add the word readonly:
private readonly TestStruct m_struct;
After that change, the code
TestClass test = new TestClass(11); Console.WriteLine("Test result: value {0}, count {1}", test.Value, test.Count);
produces this result:
Test result: value 11, count 0
Is this behavior correct or not?
We know that structs are value types, and for every member of a struct nested inside a class, the memory is allocated within the class itself. So it’s logical to assume that readonly extends down to the struct’s members as well. Unpleasant, but the compiler doesn’t tell us about this and gives no warning whatsoever that this modifier will lead to data loss.
What’s more, it would be logical to assume that since the Increment() method is declared private and only used inside the constructor, it would get inlined and be subject to the same rules as the constructor itself. Unfortunately that assumption doesn’t hold, and we simply have to remember that calling methods from within a constructor can lead to “unusual” consequences, not to mention what can happen when calling virtual methods.
Originally published 2008-12-10.
