Many developers asked why the named parameters and optional parameters were not added to the C # language until now. From the usage of other languages, they are all good features, especially when there are a lot of parameters in the method, but many of them can use the default value. Office COM APIs is an obvious example.
C # This feature is not added until 4 because there are other more urgent things to do. In addition, the named parameters and optional parameters are indeed very useful, but they also cause many problems. these problems are even more obvious when you encounter optional parameters and overloading. you may have heard the C # team say that each new language feature is initially negative comments and must be enough to attract developers to accept it. in this article, I will discuss from multiple aspects that naming parameters and optional parameters limit future component release and make the code more difficult to read. you will learn how to avoid these traps in your code and why C # occurs when these features are so late.
How to run the underlying method
Define a method:
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter = default (NameFilter ),
CityFilter cityFilter = default (CityFilter ),
AgeFilter ageFilter = default (AgeFilter)
)
This method seems to implement a lot of method overloading. You may think that it is very simple to implement the overloading of a method. In your thoughts, you think it has implemented the following methods:
Public static IEnumerable <Record> ApplyFilters (
// No parms means no filter.
)
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter = default (NameFilter)
)
Public static IEnumerable <Record> ApplyFilters (
CityFilter cityFilter = default (CityFilter)
)
Public static IEnumerable <Record> ApplyFilters (
AgeFilter ageFilter = default (AgeFilter)
)
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter = default (NameFilter ),
CityFilter cityFilter = default (CityFilter ),
)
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter = default (NameFilter ),
AgeFilter ageFilter = default (AgeFilter)
)
Public static IEnumerable <Record> ApplyFilters (
CityFilter cityFilter = default (CityFilter ),
AgeFilter ageFilter = default (AgeFilter)
)
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter = default (NameFilter ),
CityFilter cityFilter = default (CityFilter ),
AgeFilter ageFilter = default (AgeFilter)
)
In fact, it is not the above method. on the contrary, the compiler realizes that the ApplyFilters method has optional parameters and assigns values to the optional parameters when calling the method. this is why the following example does not produce ambiguity (because the first method does not set the default value for the optional parameters below)
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter
)
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter = default (NameFilter ),
CityFilter cityFilter = default (CityFilter ),
AgeFilter ageFilter = default (AgeFilter)
)
// Function call:
ApplyFilters (new NameFilter ());
This example shows that a function with a definite parameter has a higher priority than a function with an optional parameter. therefore, the first method is called above. so far, everything seems simple, and many developers know it. however, we have just opened our veil. Now, let's think about what will happen in the following example:
// NameFilter, CityFilter, and AgeFilter are inherited from the abstract class Filter.
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter = default (NameFilter ),
CityFilter cityFilter = default (CityFilter ),
AgeFilter ageFilter = default (AgeFilter)
)
{
Console. WriteLine ("First version ");
Return null;
}
Public static IEnumerable <Record> ApplyFilters (
Filter filter
)
{
Console. WriteLine ("second version ");
Return null;
}
// Sample call:
ApplyFilters (new NameFilter ());
ApplyFilters (new CityFilter ());
The parameters that call the function do not exactly match the second method. in the first call, in the first call, the parameters completely match the parameters in a method. how does the compiler handle the above situation? The first call matches the first method, and the second call matches the second method. This is already described in C # specification 7.5.3.2 (version 4.
To solve the overload problem, any optional parameter will be deleted from the method tag as long as there is no specific value. therefore, the compiler will think that the above two methods can be interpreted as the following: (when the last two optional parameters have no value)
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter = default (NameFilter ),
)
Public static IEnumerable <Record> ApplyFilters (
Filter filter
)
The first of the above two methods matches the first call more. If the called parameter is more consistent with the optional parameter of a function
If you want to force the compiler to call other methods, you need to use the name parameter:
// NameFilter, CityFilter, and AgeFilter all derive from
// An abstract Filter class.
Public static IEnumerable <Record> ApplyFilters (
NameFilter nameFilter = default (NameFilter ),
CityFilter cityFilter = default (CityFilter ),
AgeFilter ageFilter = default (AgeFilter)
)
Public static IEnumerable <Record> ApplyFilters (
Filter filter
)
ApplyFilters (filter: new NameFilter ());
ApplyFilters (cityFilter: new CityFilter ());
The second call is equivalent:
• ApplyFilters (default (NameFilter), cityFilter: new CityFilter ());
When there are inheritance relationships and virtual methods, using named parameters and optional parameters will become more complex:
Public abstract class Animal
{
Public abstract void Feed (string food = "chow ");
}
Public class Cat: Animal
{
Public override void Feed (string catfood = "cat chow ")
{
Console. WriteLine (catfood );
}
}
Public class Dog: Animal
{
Public override void Feed (string dogfood = "dog chow ")
{
Console. WriteLine (dogfood );
}
}
The following call these methods:
Var d = new Dog ();
D. Feed ();
Var c = new Cat ();
C. Feed ();
Animal thing = new Dog ();
Thing. Feed ();
D. Feed () Outputs "dog chow", c. Feed () Outputs "cat chow". thing. Feed () Outputs "chow". Explain why:
A simple explanation is that one thing is of the Animal type, so the Feed () method comes from the Animal class eclaration in the Animal class.
Now, let's look at the more complex situation. Add a new method to the dog class.
Public void Feed (string dogfood = "dog chow", bool moist = false)
{
Console. WriteLine ("{0} {1}", moist? "Moist": "dry", dogfood );
}
This new method was called when a miracle occurred, because this rule gives priority to the method in the inherited subclass.
There are still several other rules that must be examined before we even consider the ramifications of upgrading a component with these methods declared. now, let's consider a virtual method with some optional parameters. consider this change to the Cat class:
Public class Cat: Animal
{
Public override void Feed (string catfood) // parameter is not optional
{
Console. WriteLine (catfood );
}
}
// Usage:
Var c = new Cat ();
C. Feed ();
The call to this Feed () method cannot be compiled. Because there is no method without parameters in the Cat class. If it is converted to type Animal, it will work:
• Var c = new Cat ();
• (Animal) c). Feed ();
The Feed method in the Cat class currently called, because this is a virtual method.
You shoshould re-declare any of the optional parameters on each override of a virtual method to avoid confusing client code this way. when you do that, it's critically important that you ensure the default value of that parameter is the same for each override. otherwise, these two cballs cocould use different values for the food parameter.
The rules that cover default parameters in interface methods are very similar to the rules for base class methods. that carries all the way through: If you create an overloaded method with the same name as a method in an interface, the overloaded method will be preferred to the interface method, but only if you 've implemented the interface method explicitly.
Releasing new Versions
Now that we 've covered the simple case, let's examine what happens when new versions of components are delivered. remember that one of C #'s original goals was to be a 'component oriented mode', meaning that well-written C # assemblies shoshould be safely upgradeable on an individual basis. these new features in the C # language have increased the potential for breaking changes when you deliver upgraded components. with each of these changes, code behavior may change at compile time or runtime. compile time breaks will show up only when developers using the component build an updated version. runtime breaks will show up when users have the new component installed and run the application. some changes can cause both.
Let's start with the obvious: changing the name of any parameter on a public, or protected method is a compile time breaking change. what else C # developers don't know is that this has been a breaking change for some time. even though C # only added support for named and optional parameters in C #4, other versions on. NET Framework, have had this features for some time (most obviusly, Visual Basic ). anyone using your component from one those ages wocould be affected by your changes earlier.
Next, of course, changing the values of default parameters creates a breaking change. this change cocould cause breaks at either compile time, or runtime, depending on how you code the change. the default value of an optional parameter is inserted by the compiler at the callsite. at runtime, any methods that were compiled with the previous version of the component will continue to have the previous value inserted at the callsite. however, when you recompile any caller, the default value will change. it's really impossible to avoid this as a breaking change, you must pick whether the break is discovered at compile time, or runtime. method callcompiled before the update will continue to use the previous default value; method callrecompiled with the new version of the component will use the new default value. how your method interprets the old and new values of the optional parameter determines which version has changed behavior.
All those issues I brought up earlier regarding method overloads, methods declared in base classes, and methods declared in interface methods all apply here as well. modifying the default values of any parameter or the number of default parameters will affect the better method choice made by the compiler. that won't have any runtime breaking changes, but cocould potentially introduce any number of breaking compile time changes. of course, calling a different method cocould change the runtime behavior as well.
Notes
It does not mean that you should not use optional parameters. you must remember and avoid these traps. if you find that you have made many optional parameters in the method, you may consider your method design. we recommend that you use optional parameters with caution. most importantly, do not use optional parameters in Virtual Methods and interfaces.
From JustRun1983