Question

Got into a tricky situation in using optional parameters in tandem with method overriding and interfaces in C#. I have read this.

Just wanted to add another dimension to the whole picture. There were quite a few code illustrations in that post. I picked up the one involving tags by VS1 and added another dimension to it as it had interfaces as well as inheritance being demonstrated. Though the code posted over there does work and displays the appropriate string as found in the sub class, base class, and interface, the following code does not.

void Main()
{
    SubTag subTag = new SubTag();
    ITag subTagOfInterfaceType = new SubTag();
    BaseTag subTagOfBaseType = new SubTag();

    subTag.WriteTag();
    subTagOfInterfaceType.WriteTag();
    subTagOfBaseType.WriteTag();
}

public interface ITag
{
    void WriteTag(string tagName = "ITag");
}
public class BaseTag :ITag
{
    public virtual void WriteTag(string tagName = "BaseTag") { Console.WriteLine(tagName); }
}

public class SubTag : BaseTag
{
    public override void WriteTag(string tagName = "SubTag") { Console.WriteLine(tagName); }
}

And the output is

SubTag
ITag
BaseTag

So, it appears that the type of reference holding the reference to the inherited/implemented subclass does matter in determining which optional parameter value gets picked up.

Has anyone faced similar issue and found a solution? Or has C# has got some workaround for this in the later releases? (The one I am using is 4.0)

Thanks.

Was it helpful?

Solution

The C# team did not like adding optional arguments to the language, this is a rather good demonstration why.

It helps to understand how they are implemented. The CLR is quite oblivious of the feature, it is implemented by the compiler. If you write a method call with a missing argument then the C# compiler actually generates the code for the method call with an argument, passing the default value. Easy to see with ildasm.exe.

You can see this back in the language rules, the optional value must be a constant expression. Or in other words, a value that can be determined at compile time. You cannot use the new keyword or an expression that uses variables. Required so the compiler can embed the default value in the assembly metadata. It will need it again when it compiles a call to a method with optional arguments that's declared in another assembly.

The friction here is that the compiler cannot figure out which virtual method is actually going to be called at runtime. Dynamic dispatch is a pure runtime feature.

So all it reasonably can go by is the declared type of the object reference. You used all three versions so you get all three default argument values.

OTHER TIPS

I think optional parameters are syntactic sugar only. So they get picked up at compile time. The compiler doesn't know the actual types of the objects, so the optional values are picked up based on the type of the reference.

If you need this behavior, then you can provide two different methods, one with the parameter and one without, then you can implement the parameterless method differently in different implementations. Of course this only works for fixed parameter layouts.

Update: tested and confirmed, given a void x(int z = 8) method, the method call x() is compiled to x(8), so the parameter values are baked in as constants.

A common way to solve this is to have a special "sentinel" value (often null) which the implementing methods recognise and substitute with the desired value.

For your example, it might look something like this:

public interface ITag
{
    void WriteTag(string tagName = null);
}

public class BaseTag :ITag
{
    public virtual void WriteTag(string tagName = null)
    {
        if (tagName == null)
            tagName = "BaseTag";

        Console.WriteLine(tagName); 
    }
}

public class SubTag : BaseTag
{
    public override void WriteTag(string tagName = null)
    {
        if (tagName == null)
            tagName = "SubTag";

        Console.WriteLine(tagName);
    }
}

Then your test code will output

SubTag
SubTag
SubTag

which I think is what you want?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top