One man's constant is another man's variable

September 14, 2008 19:18

There’s probably only one good reason for a type to keep its state unchangeable (apart from using it as a key in a hash table). This reason is multithreading.

Unchangeable state means the type methods designed to update the instance will instead return a new-minted one. By and large, this is immutability: you manacle yourself in terms of performance but gain from ease of multithreading development. Quite straightforward, isn’t it – all multithreading issues appear because you have to make sure only one thread at a time can update an object, but immutable objects don’t need synchronization at all, huh![1]

So, the overall idea is somewhat familiar to most of us but very soon everybody will need to understand the basics of immutability – because Eric Lippert said: "Immutable data structures are the way of the future in C#", and Eric Lippert does understand what he is talking about[2].

So, let's peel off the tasteless wrappings and see what we already knew.

Immutability roots.

The most popular immutable type is good old System.String. We write

"foo".Replace("f", "b");

but nothing displeasing for "foo", the method creates and returns a new string. As time went by, we understood the need for StringBuilder, but also we learned the captivating benefits:

  •  No need for string synchronization in multithreaded environment
  •  Possibility to use strings as keys in hashtables, because the keys should not change (in fact, hashtable keys can have just an immutable part so that the hash would be calculated only from it)

Recently (?), a couple of other immutable approaches have appeared in C#.

Anonymous classes.

We write

       var foo = new { Name = "foo" };                            

and behind the scenes the C#3 compiler generates a class with read-only properties that can’t be changed. Anonymous types are heavily used in hashtables inside LINQ, so that’s why they are immutable. Interestingly enough, anonymous types of VB9 are fully mutable and the following code will compile and run

       Dim foo = New With { .Name = "foo" }
       foo.Name =
"boo"

Good news is that poor VB devs are not completely bereft of the possibility of using anonymous types as hashtable keys, although they need a new keyword trickery for that. Hash value will be calculated from the (read-only) fields marked with the "Key" keyword:

       Dim foo = New With { Key .Name = "foo", .Index = 1 }
       foo.Name =
"boo" 'won’t compile.


Closures.

Second example of immutability is closures, introduced in C#2 and enhanced with lambda expressions of C#3.  If you’re not familiar with the idea, I really recommend learning it. One can write very pithy and nice code, but still need to understand what’s going on behind the curtain. So, take Reflector and Ildasm and dive into a morass of autogenerated code (alternatively, read some articles on the topic[3]). 

Perhaps, only lazybones haven’t written about the inevitable pitfalls you’d face in this field:

    internal static List<Action> GetActions(List<string> strings)
    {

        var result = new List<Action>();
        foreach(string s in strings) { result.Add(() => Console.WriteLine(s)); }

        return result;
    }

     static void Main()
    {
        var strings = new List<string> {"one", "two", "three"};
        GetActions(strings).ForEach(a => a());

    }

Smell a rat? Three times "three"? Calm down, it’s not that bad yet. Firstly, this can be easily fixed (at least two possibilities). Secondly, Resharper eeks an "access to modified closure" warning for the lucky guys who have it installed.

So, allow me to replenish your glass and let’s finish off this milk tooth post with the following admonitory code sample:

    class Developer
    {
        public string Name { get; set; }
        public Developer(string name) { Name = name; }
    }

    static void Main()

    {
        var team = new List<Developer> {
                new Developer("Кастусь"),
                new Developer("Пятрусь")
        };

        var writeNames = new List<Action>();
        team.ForEach(i => writeNames.Add(() => Console.WriteLine(i.Name)));

        foreach (var action in writeNames) { action(); }

        Console.WriteLine("\nOriginal team was changed\n");

        team[0] = new Developer("Foo");
        team[1].Name = "Boo";

        foreach (var action in writeNames) { action(); }
    }

Кастусь was left intact but Пятрусь wasn’t: while changing the team we altered the second object.

If we used mere strings instead of a custom type, everything would have been ok. Thus, if you want to rely on the immutability of closures, you might want to make sure they operate with immutable data structures.




Footnotes.

[1]  –  Well, we'd need some at the system level. At least to make sure memory regions
            allocated by different threads would not overlap. But it's another day's post. 

[2]  – 
Fabulous adventures in immutability from Eric Lippert.

[3]  –  A nice write-up about anonymous methods from Patrick Smacchia.


Comments

Comments are closed