I'm aware that a params
modifier (which turns in one parameter of array type into a so-called "parameter array") is specifically not a part of the method signature. Now consider this example:
class Giraffid
{
public virtual void Eat(int[] leaves)
{
Console.WriteLine("G");
}
}
class Okapi : Giraffid
{
public override void Eat(params int[] leaves)
{
Console.WriteLine("O");
}
}
This compiles with no warnings. Then saying:
var okapi = new Okapi();
okapi.Eat(2, 4, 6); // will not compile!
gives an error(No overload for method 'Eat' takes 3 arguments
).
Now, I know that the compiler translates the params
modifier into an application of the System.ParamArrayAttribute
onto the parameter in question. In general there's no problem in applying one collection of attributes to a parameter of a virtual method, and then decorating the "corresponding" parameter in an overriding method in a derived class with a different set of attributes.
Yet the compiler chooses to ignore my params
keyword silently. Conversely, if one makes it the other way round, and applies params
to the parameter in the base class Giraffid
, and then omits the keyword in the override in Okapi
, the compiler chooses to decorate both methods with the System.ParamArrayAttribute
. I verified these things with IL DASM, of course.
My question:
Is this documented behavior? I have searched thoroughly through the C# Language Specification without finding any mention of this.
I can say that at least the Visual Studio development environment is confused about this. When typing the 2, 4, 6
in the above method call, the intellisense shows me void Okapi.Eat(params int[] leaves)
in a tip.
For comparision, I also tried implementing an interface method and changing the presence/absence of params
in interface and implementing class, and I tried defining a delegate type and changing params
or not in either the delegate type definition or the method whose method group I assigned to a variable of my delegate type. In these cases it was perfectly possible to change params
-ness.
Abstract is used to define something that requires additional definition of functionality before it is considered "complete" (or concrete in Java-certification-test-terms). Generic means it's a class that can handle a wide variety of data types that you define when you instantiate the class.
A generic class or structure can contain nongeneric procedures, and a nongeneric class, structure, or module can contain generic procedures. A generic procedure can use its type parameters in its normal parameter list, in its return type if it has one, and in its procedure code.
Abstract Class Now generics seem cool but they really shine when used with abstract classes. An abstract class is a class that itself is never intended to be instantiated, instead they are used to pass properties to sub classes via inheritance.
Declaring Variant Generic InterfacesYou can declare variant generic interfaces by using the in and out keywords for generic type parameters. ref , in , and out parameters in C# cannot be variant. Value types also do not support variance. You can declare a generic type parameter covariant by using the out keyword.
The compiler's behaviour is correct, but this is a bit of a mess. I would have preferred this to be at least a warning.
It is unsurprising that you can't find where in the spec it says this is correct. The relevant bits are:
The binding-time processing of a method invocation of the form M(A), where M is a method group, and A is an optional argument-list, consists of the following steps: The set of candidate methods for the method invocation is constructed. For each method F associated with the method group M, if F is non-generic, F is a candidate when M has no type argument list, and F is applicable with respect to A.
What are the "methods associated with the method group M"? Well, first off, what is a method group?
A method group, which is a set of overloaded methods resulting from a member lookup...
OK, so what are the member lookup rules?
Otherwise, the set consists of all accessible members named N in T, including inherited members and the accessible members named N in object. Members that include an override modifier are excluded from the set.
Emphasis added.
The practical upshot here is that for the purpose of overload resolution, an overridden method is considered to be the method that was originally declared, not the method that was overriding. This rule is, unfortunately, violated in this case:
virtual void M(int x, int y) { }
...
override void M(int y, int x) { }
...
M(x = 1, y = 2);
Overload resolution uses the names from the more derived version. This is an unfortunate consequence of the fact that named arguments were added very late in the game.
In short: for the purposes of determining whether a method is "params" or not, the analysis is done on the original method, not on the overriding method.
It would have been nice had the compiler given you a warning here.
can say that at least the Visual Studio development environment is confused about this
Correct. The IntelliSense layer always displays the method information for the overriding method, not the overridden method. Research showed that users found it confusing when methods were made to look as though they were the originally declaring method, not the overriding method. And of course, as I mentioned before, those are the parameter names you're going to use for named arguments.
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