28 Jan
2010

C# 4.0 Optional Parameters – Exploration.

Category:UncategorizedTag: , :

{? Removed big long story about how I ended up writing this post which provides no value to the blog?}

Summary of big long story to at least give a little context as to why (yet another post on optional parameters):

I threw an idea out to the Moq discussion group of how we could use the named/optional parameters in a future version of Moq. (you can read the thread here) In my original feature request I displayed my lack of concrete knowledge in the named/optional parameters support that is eventually coming with .net 4.0.

Once I learned that you could place default values on interfaces it left me with questions? So, what better way to figure them out? Go test it?

Disclaimer: (Shouldn?t every blog have some context enlightening disclaimer?)
I haven?t looked up best practices or lessons learned from people that have had this language feature (VB), so I?m just doing this as an experiment for myself. Hope some of my findings help the other C#?ers wanting to learn a little about the feature.

What are optional parameters?

DimeCasts.Net, Derik Whittaker has a nice intro video # 153 – Exploring .Net 4 Features – Named and Optional Parameters

OR check out – http://tinyurl.com/yz3pc9o

 

Can an interface define a default value?

Yes!
image

 

Can I specify a default in the concrete implementation, if the interface has a default also?

Yes!

image

What happens when the concrete implementation has a different default value than the interface?s default?

If the interface has a default value specified, that is different from the concrete implementation, then it depends on what reference you?re using when executing the method.

image

In the case below we are executing the method directly off of the Foo instance and will therefore get the concrete implementation?s default value when executing.

(new Foo()).Bar() ? would use the value of ?1000?.

And in the case below we cast the Foo instance to an IFoo and it will then use the interfaces default value when executing.

((IFoo) new Foo()).Bar() ? would use the value of ?1?.

Below are some examples of the different use cases.

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void Should_get_the_concrete_class_default_value()
    {
        Foo f1 = new Foo();
        f1.Bar();
        f1.ParamValue.ShouldBeEqualTo(1000);
    }

    [TestMethod]
    public void Should_get_the_interface_default_value()
    {
        IFoo f = new Foo();
        f.Bar();
        f.ParamValue.ShouldBeEqualTo(1);
    }

    [TestMethod]
    public void Should_get_the_interface_default_value_because_of_explicit_cast()
    {
        Foo f = new Foo();
        ((IFoo)f).Bar();
        f.ParamValue.ShouldBeEqualTo(1);
    }

    [TestMethod]
    public void Should_get_the_concrete_class_default_value_because_of_explicit_cast()
    {
        IFoo f = new Foo();
        ((Foo)f).Bar();
        f.ParamValue.ShouldBeEqualTo(1000);
    }
}

interface IFoo
{
    int ParamValue { get; }

    void Bar(int paramValue = 1);
}

class Foo : IFoo
{
    public int ParamValue { get; private set; }
    public void Bar(int paramValue = 1000)
    {
        ParamValue = paramValue;
    }
} 

 

The next experiment – Extract Interface.

Next I tried removing the IFoo interface that I?d created manually, because I wanted to exercise the ?Extract Interface?? functionality, just to see how it dealt with the these defaults.

Luckily, there were no surprises. The interface it created was exactly (less spacing) the same as I originally had.

Although it didn?t display the default constant value in the dialog during creation, there was a hint that the method signature had a default by placing [] around the int resulting in ?Bar([int])?.

image

Side Tool Issue: Can?t say I like how it forced me to put the interface in a different file, I guess it?s enforcing ?best practice? here, but I prefer to do this later in the dev cycle than immediately (kind of like how R# allows you to place in the file next to the original class). #ToolGettingInWay

Optional Parameter Issue: One issues I see with this solution was the dirty/icky copy/paste feeling I got when extracting the interface ? the default was copied from the class to the interface.

Possible solutions to the ?dirty/icky copy/paste feeling? the extract interface gives.

(in no particular order of preference)

  • Place all defaults into a constant and reference the constant in both the interface and the concrete implementation(s).
  • Don?t place the defaults in the concrete implementation (only in the interface). As you should probably not be depending on the concrete implementation to begin with, you wouldn?t need it there (and wouldn?t even call it). This would also help in the case that there are multiple concrete implementation and having to sift through the code looking for all instances to updated defaults for could be very error prone.

On the surface named parameters seem like a very simple feature to the C# language. But after delving into the feature a little, I can see there are many complicated scenarios you can get your self caught up into.

As with anything?Use with care!

10 thoughts on “C# 4.0 Optional Parameters – Exploration.

  1. Not so sure I like that the default value uses the interface one when when you do this:

    ((IFoo) new Foo()).Bar() – would use the value of ‘1’.

    Seems like it violated the principle of least surprise. Maybe it is just me though.

  2. Agree with John. This behavior is really annoying. It may introduce very subtle bugs. I wonder if this feature actually brings more good or more evil.

  3. First question:
    in your example above, you were able to get different default values, depending on what you casted Foo to. Would this work the same way the following situation?

    IFoo firstFoo = new Foo(); firstFoo.Bar() // 1
    Foo secondFoo = new Foo(); secondFoo.Bar() // 1000

    Second question:
    How would method Overloading work in this situation?

    public interface IFoo
    {
    int ParamValue { get; }
    void Bar();
    void Bar(int paramValue = 1);
    }

  4. @Joey
    First question – Yes – take a look at the first two unit tests in this post…
    – Should_get_the_concrete_class_default_value
    – Should_get_the_interface_default_value

    Second Question – What would you expect this to do?

    I expected the call to Bar() to call the correct bar (not the one with a default parameter) Not sure why you would want to provide two methods of the same name where one has a default. I think the idea of the parameter defaults is so you don’t have to provide extra overloads.

    I tested it and the two following tests both threw NotImplementedExceptions.

    public interface IFooJoey
    {
    int ParamValue { get; }
    void Bar();
    void Bar(int paramValue = 1);
    }

    public class FooJoey : IFooJoey
    {
    public int ParamValue { get; private set; }

    public void Bar()
    {
    throw new NotImplementedException();
    }

    public void Bar(int paramValue)
    {
    ParamValue = paramValue;
    }
    }

    [TestClass]
    public class UnitTest1Joey
    {
    [TestMethod]
    public void Should_get_the_concrete_class_default_value()
    {
    FooJoey f1 = new FooJoey();
    f1.Bar();
    f1.ParamValue.ShouldBeEqualTo(1);
    }

    [TestMethod]
    public void Should_get_the_interface_default_value()
    {
    IFooJoey f = new FooJoey();
    f.Bar();
    f.ParamValue.ShouldBeEqualTo(1);
    }
    }

  5. @Steve Py
    Actually, this feature brings it more inline with c . It’s one of the few things I’ve missed when I switched to C#.

Comments are closed.