Friday, January 22, 2016

C# Interfaces and Default Parameters

How does a default parameter work?

One of the things that I love about C# is how so many of it's features are just very conveniently designed compiler tricks. This means that, just like any other magic trick, once you know how the trick is performed you immediately realize that there is nothing magical about it all!

So, let's talk about default parameters. They are actually just constant values that get compiled into your code when you go to use a method that has them. Let's look at an example...

public class DefaultParamTests1
{
    [Fact]
    public void WhatYouWrite()
    {
        var actual = Double();
        Assert.Equal(2, actual);
    }
 
    private static int Double(int i = 1)
    {
        return i * 2;
    }
}
 
public class DefaultParamTests2
{
    [Fact]
    public void WhatItCompilesTo()
    {
        var actual = Double(1);
        Assert.Equal(2, actual);
    }
 
    private static int Double(int i)
    {
        return i * 2;
    }
}

What happens when interfaces and methods don't match?

So, now that you know how the trick is performed, what happens if you use a different default value for a parameter defined by an interface and a class?

The answer is simple: if your object is cast as the class, then it will use the class value. If your object is cast as the interface, it will use the interface value instead. Let's take a look at another example...

public interface INonMatchingDemo
{
    int Double(int i = 1);
}
 
public class NonMatchingDemo : INonMatchingDemo
{
    public int Double(int i = 2)
    {
        return i * 2;
    }
}
 
public class DefaultParamTests3
{
    [Fact]
    public void CastAsClass()
    {
        var demo = new NonMatchingDemo();
        var actual = demo.Double();
        Assert.Equal(4, actual);
    }
 
    [Fact]
    public void CastAsInterface()
    {
        INonMatchingDemo demo = new NonMatchingDemo();
        var actual = demo.Double();
        Assert.Equal(2, actual);
    }
}

Best Practice Recommendation

So, what happens when your class and interface default values have gotten out of sync? This will usually lead to a bug that is be very intuitive to find.

Whenever I write interfaces that uses default parameters, I implement the class methods to note provide a default value. That way there will never be any chance of them getting out of sync!

public interface IBestPracticeDemo
{
    int Double(int i = 1);
}
 
public class BestPracticeDemo : IBestPracticeDemo
{
    public int Double(int i)
    {
        return i * 2;
    }
}
 
public class DefaultParamTests4
{
    [Fact]
    public void CastAsClass()
    {
        var demo = new BestPracticeDemo();
        var actual = demo.Double(1);
        Assert.Equal(2, actual);
    }
 
    [Fact]
    public void CastAsInterface()
    {
        IBestPracticeDemo demo = new BestPracticeDemo();
        var actual = demo.Double();
        Assert.Equal(2, actual);
    }
}

Enjoy,
Tom

1 comment:

  1. typo: "to note provide" should be "to *not* provide"

    ReplyDelete

Real Time Web Analytics