Friday, April 29, 2005

 

Mutable Structs

Mutable Structs.

One interesting thing about struct is when you pass them to an ArrayList. Take a look at this code:
using System;
using System.Collections;

public class MutableStruct
{

class Foo
{
public int x;

//public Foo() { x = -1; }

public void Set(int x_)
{ x = x_; }
}

public static void Main()
{
Foo[] fa = new Foo[10];

fa[0] = new Foo();
fa[0].Set(2);


Console.WriteLine(fa[0].x);

// Set an ArrayList of Foo
ArrayList faL = new ArrayList();

faL.Add(new Foo() );
((Foo)faL[0]).Set(2);

Console.WriteLine(((Foo)faL[0]).x);

}
}
The result will be 2 for both as expected. Because Foo is a class, a reference was passed to the ArrayList.

But if you do the following with struct, you get an astonishing answer:
using System;
using System.Collections;

public class MutableStruct
{

struct Foo
{
public int x;

//public Foo() { x = -1; }

public void Set(int x_)
{ x = x_; }
}

public static void Main()
{
Foo[] fa = new Foo[10];

fa[0] = new Foo();
fa[0].Set(2);


Console.WriteLine(fa[0].x);

// Set an ArrayList of Foo
ArrayList faL = new ArrayList();

faL.Add(new Foo() );
((Foo)faL[0]).Set(2);

Console.WriteLine(((Foo)faL[0]).x);

}
}
The value for ((Foo)faL[0]).x was 0 instead of the expected 2. What went “wrong”?

How does a struct differ from a class?

A struct is a value type. What this means is that the object of a struct is placed on the program stack instead of in the heap. This also means that when you copy a struct, or when you assign an object of a struct to another, or when you pass it as an argument to a method, a copy is made. This is in contrast with a class, because with a class, a reference (a pointer) is passed.

However, when you called the Set method of the struct, a secret "ref this" was passed to the method, allowing you to mutate the value of the struct. (This is what mutable struct means). So (this is my interpretation) the actual Set method looks like this:
public void Set(ref this, int x_)
{
this.x = x_;
}
And when you called the Set method, it looks like this fa[0].Set(fa[0], 2). In this case a reference of the struct object (fa[0] is the reference to the first struct object in the Foo array) was passed into the Set method.

So what went “wrong” with the above code is when we called ((Foo)faL[0]).Set(2);, we first invoke the indexer of the ArrayList object, which is nothing but the get/set assessors method. The get method will probably have the following code
return __someObject[indexOfArray];
When this occur, a copy of the Foo object is made. Just like any normal method that returns a value, the value is first copied to a temporary variable and returned.

So faL[0] returns a copy of our Foo object. When we attempt to call the method ((Foo)faL[0]).Set(2). This method will then jam the value 2 into the copied version of our object. Then when the the ((Foo)faL[0]).Set(2); method returns, the copy is destroyed.

The following statment Console.WriteLine(((Foo)faL[0]).x); will yield an value of 0 because the original ((Foo)faL[0]).x hasn’t changed a bit. (A copy of it was returned at both instances:
((Foo)faL[0]).Set(2);
Console.WriteLine(((Foo)faL[0]).x);
For a class Foo a reference was returned instead, allowing us to change its field.

The moral of the story is don’t use mutable structs. Use a class instead, or return a new struct.
Comments: Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?