Wednesday, October 23, 2013

Learning C# via Katas - Part 2 - Extension Methods

This is part 2 in a series of blog posts exploring CSharpKatas, a new GitHub project and training tool I put together.  The Katas teach C# newcommers how to apply advanced language features, how to write idiomatic C#, and most importantly how to avoid Java idioms.

If It Types Like a Duck


Kata #1 introduced the reader to LINQ and some replacements for loops and if statements.  Kata #2 focuses on a feature typically found in dynamic languages that allows adding methods to other people's classes without modifying or subtyping the class.  The feature, called extension methods, introduces to C# elements of a feature known as "duck typing" found in languages such as Ruby or JavaScript.

Duck typing is a style of typing in which an object's methods and properties determine the valid semantics, rather than its inheritance from a particular class or implementation of a specific interface. The name of the concept refers to the duck test, attributed to James Whitcomb Riley, which may be phrased as follows:
When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck

Unlike with dynamic languages, however, C#'s extension methods are implemented via some fancy compiler trickery.  The details of the trickery is captured in the third todo in the Kata in which the reader is asked to reverse engineer the Intermediate Language (IL) code after compiling the solution.  This final todo will also add a powerful tool to the reader's toolbelt.

Caveats and Limitations


Due primarily to the compiler trickery mentioned above extension methods have drawbacks over what dynamic language aficionados would likely expect.  For instance isn't possible to add static methods onto other people's classes.  Additionally, one can't add properties to external classes.  This last limitation has frustrated me on more than one occasion.

Another limitation is that extension methods require both knowing the namespace an extension method is defined in, and adding a related using statement.  This limitation can make it hard for others to discover your extension methods.  It can also decrease readability as it can be difficult to determine where a method actually lives.

Finally a caveat: it can be easy to over use extension methods after first learning of them.  For instance just because you could add a SpellCheck() method onto C#'s String class, would you really want this in your code:

foreach (var error in "I'm a bad speller".SpellCheck())
{
    // The above is a bad idea, please don't do it
}

Kata #2


With the caveats and drawbacks out of the way here is a copy of the second Kata:

using System.Collections.Generic;
using NUnit.Framework;

namespace CSharpKatas
{
    // todo #1: Without modifying SomeoneElsesClass add a new class that 
    //     makes the unit tests pass
    // todo #2: Refactor so that there are no loops or if statements.
    // todo #3: (see below)


    #region DoNotModify
    // DO NOT MODIFY THIS CLASS
    public class SomeoneElsesClass
    {
        public List<int> Numbers { get; set; }
    }
    #endregion

    [TestFixture]
    public class TestExtensionMethods
    {
        [Test]
        public void FindNumberOrDefault_NumberExists_ReturnIt()
        {
            var someoneElsesClass = new SomeoneElsesClass
            {
                Numbers = new List<int> { 1, 2, 3 }
            };
            Assert.AreEqual(1, someoneElsesClass.FindNumberOrDefault(1));
        }

        [Test]
        public void FindNumberOrDefault_NumberDoesNotExist_ReturnNull()
        {
            var someoneElsesClass = new SomeoneElsesClass
            {
                Numbers = new List<int> { 1, 2, 3 }
            };
            var v = someoneElsesClass.FindNumberOrDefault(4);
            Assert.IsNull(v);
        }

        [Test]
        public void FindNumberOrDefault_SomeoneElsesClassIsNull_CheckForNull()
        {
            // todo #3: What's bizarre about the following test? Why is there 
            //    no error? Consider reverse engineering the code with a tool 
            //    like Just Decompile to get the answer.
            SomeoneElsesClass someoneElsesClass = null;
            Assert.IsNull(someoneElsesClass.FindNumberOrDefault(4));
        }
    }
}


Summary


I hope you find these Kata's fun and enlightening and that you find my enthusiasm for the C# language contagious.  Enjoy!  And feel free to ask questions in the comments here or @lprichar on twitter.

No comments: