I've been using optional parameters in a limited fashion for some time now with .NET 4. I've recently thought about how the optional parameters are implemented and something dawned on me.
Take the following example:
public interface ITest
{
void Go(string val = "Interface");
}
The only requirement for implementing this is that the largest form of the signature void Go (string val)
be implemented, in fact we need not implement the optional parameter because the compiler takes care of that wire-up (although the option to use optional parameters will not be available when using the concrete class directly).
With this said, to provide the functionality in both places, and for it to be discover-able in the implementation, one could implement the optional declaration in both interface AND implementation. In fact, the productivity tool ReSharper will pull this optional declaration up to the concrete type automatically when implementing an interface. This seems the logical thing to do. However...
What's to stop us from using different values in the concrete type and interface? This set off my alarm bells this morning as if someone goes in to change that value, and forgets to persist it down the inheritance of overrides / interfaces, the behaviour will be completely different depending on how you access the object. This could be very frustrating to debug.
Try this LINQpad Script:
void Main()
{
IA iface = new A();
A cls = new A();
iface.Go();
cls.Go();
}
interface IA
{
void Go(string val = "Interface");
}
class A : IA
{
public void Go(string val = "Class") { val.Dump(); }
}
The output will be:
Interface
Class
Which brings me to my actual question:
What (if any?) way can we safeguard this, without losing the ability to use the optional parameter variant from the concrete class, and so it's discoverable / readable to the coder?
Has anyone encountered this problem before? How did you solve it? Is there any best practices that could help prevent this problem from becoming prevalent in a multi-developer large-scale codebase?
Nothing stops you from doing that. Using optional parameters bakes the value into the method call. So as you are seeing, calling IA.Go()
and A.Go()
give you different values because they're compiled to IA.Go("Interface")
and A.Go("Class")
.
The way around it (meaning to only supply the "default" parameter in the implementation) is to have an overload instead of optional parameters:
interface IA
{
void Go();
void Go(string val);
}
public class A : IA
{
public void Go()
{
Go("Class");
}
public void Go(string val) { val.Dump(); }
}
Some other options:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With