In the previous blog post, I mentioned the new language features in C #4.0, the "optional parameter", but it is still not satisfied after being written. I still have some questions that I have not explained. As a result, I made some explorations and found many interesting questions behind such a small language feature. This time, we will record the findings and questions in the exploration process.
Implementation of "optional parameters"
In a cnblogs article, Jiang Jinnan mentioned: "The default parameters are embodied in two special custom features: optionalattribute and defaultparametervalueattribute ". To verify the correctness of this statement, I did some experiments myself.
The best way to study the implementation principle of language features is to decompile Il.CodeTo find out. So let's proceed with this clue.
First, use the C # code to write a simple test method:
Public VoidTestmethod (StringSTR ="")
{
}
As mentioned in the previous article, this method has the same effect as using optionalattribute and defavaluvalueattribute directly.
Public VoidTestmethodwithattributes ([optional, defaultparametervalue ("")]StringStr)
{
}
The two pieces of code compile the Il except the name. The following uses the first method as an example. Its Il is like this:
. MethodPublicHidebysig instanceVoidTestmethod ([opt]StringStr) cel managed
{
. Param [1] =""
// Code size 2 (0x2)
. Maxstack 8
Il_0000: NOP
Il_0001: Ret
} // End of method program: testmethod
The generated metadata is as follows:
Methodname: testmethod (06000003)
Flags: [public] [hidebysig] [reuseslot] (00000086)
RVA: 0x0000205b
Implflags: [il] [managed] (00000000)
Callcnvntn: [Default]
Hasthis
Returntype: void
1 arguments
Argument #1: String
1 Parameters
(1) paramtoken: (08000002) Name: Str flags:[Optional] [hasdefault](00001010) Default: (string)
To be honest, I didn't fully understand the above two "Books of heaven", but I still found some exceptions and thought some things were wrong. Why did I say that? This is because the general attribute compilation result is not like this. For example:
First, define an attribute that can only be applied to parameters:
[Attributeusage (attributetargets. parameter, inherited =False, Allowmultiple =True)]
Sealed ClassMyattribute: attribute
{
}
Then define a method modified by this attribute:
Public VoidTestattribute ([my]StringStr)
{
}
The IL after compilation of this method is as follows:
. MethodPublicHidebysig instanceVoidTestattribute (StringStr) cel managed
{
. Param [1]
. Custom instanceVoidHowdidtheyimplementoptionalparameters. myattribute:. ctor () = (01 00 00)
// Code size 2 (0x2)
. Maxstack 8
Il_0000: NOP
Il_0001: Ret
} // End of method program: testattribute
The red part in the above Code is not found in the Il of testmethod. In addition, its metadata and testmethod are also different:
Methodname: testattribute (06000005)
Flags: [public] [hidebysig] [reuseslot] (00000086)
RVA: 0x00002061
Implflags: [il] [managed] (00000000)
Callcnvntn: [Default]
Hasthis
Returntype: void
1 arguments
Argument #1: String
1 Parameters
(1) paramtoken: (08000004) Name: StrFlags: [none](00000000)
Customattribute #1 (0c000010)
-------------------------------------------------------
Customattribute type: 06000001
Customattributename: howdidtheyimplementoptionalparameters. myattribute: instanceVoid. Ctor ()
Length: 4
Value: 01 00 00> <
Ctor ARGs :()
The metadata of this method has an additional description of customattribute, and its flags are empty. Unlike the flags of testmethod, there are signs like [Optional] [hasdefault.
Since I have not read the ECMA 335 documentation, I just want to make a less cautious assumption: optionalattribute and defaultparametervalueattribute are different from other attributes, and they have their own proprietary flags. When the code that calls testmethod is compiled, the compiler reads the default value stored in metadata and embeds the read value into Il.
No trace of optionalattribute and defaultparametervalueattribute is found in the C # code of testmethod, the compiled il code, and its metadata, therefore, I think it is open to question that "the default parameters are embodied in two special custom features: optionalattribute and defaultparametervalueattribute.
Traps
"Optional parameter" seems convenient and easy to use, but is it really a great choice to use it? Actually not. There are at least two traps hidden behind it (I only found two traps ).
First trap: Version change
Taking the testmethod mentioned above as an example, write a method to call it:
Public VoidCaller ()
{
Testmethod ();
}
No parameter is input here, which is equivalent to the default parameter "". The IL compiled by caller is as follows:
. MethodPublicHidebysig instanceVoidCaller () cel managed
{
// Code size 14 (0xe)
. Maxstack 8
Il_0000: NOP
Il_0001: ldarg.0
Il_0002: ldstr""
Il_0007: Call instanceVoidHowdidtheyimplementoptionalparameters. Program: testmethod (String)
Il_000c: NOP
Il_000d: Ret
} // End of method program: caller
Note that in the red lines, the value of "a" is actually written to the Il of caller. That is to say, if there is a non-strong name containing "optional parameters ",ProgramThe default value of the parameter is modified during version upgrade. If the other Assembly that references the parameter is not re-compiled, the new default parameter value cannot be obtained, the value defined in the old version will still be passed in at runtime.
Second trap: Cross-language calls
Not all languages are forced to support the "optional parameter" feature. For languages that do not support this feature, you can ignore the default values contained in the metadata and force users in this language to explicitly provide parameter values. This will cause inconsistent behavior during code running.
All Versions earlier than C #4.0 do not support "optional parameters. That is to say, if C #4.0 syntax and. the framework of Net Framework 2.0 compiles an assembly containing "optional parameters". If you reference this Assembly in the vs2008 project, you can only explicitly provide the parameter values.
In view of the above two points, I think the following principles should be followed when using "optional parameters": Public APIs (including public members and public protected members) do not use "optional parameters", but use method overloading to avoid inconsistent API behavior. Enjoy the private APIs in the Assembly.
About CLS-compliant
The Microsoft one-stop sample code library document mentions that "optional parameters" are not CLS-compliant. I think this is wrong. The simplest verification method is to add clscompliantattribute.
Add the following line to assemblyinfo. CS of the project that contains testmethod (testmethod must be a public method here, because clscompliant only applies to public APIs:
[Assembly: clscompliant (True)]
Then compile, the compiler does not give any warning. If the unit type is used in the public API, the compiler will not hesitate to give a warning. For example:
Public VoidTestclscompliant (UintParameter)
{
}
A warning is displayed during compilation: argument type 'uint' is not CLS-compliant.
In addition, the msdn document also mentions that although "optional parameters" are not included in the CLS specification, CLS can "tolerate" its existence.
Possible bugs in Reflector
All of the above decompilers are implemented using IL dasm, and if the latest version of reflector is used (that is, the version that can only be used for 14 days) to view the decompiled C # code (set the version to any non-none value), you will find that it interprets testmethod as using optionalattribute and defaultparametervalueattribute. I suspect that this is because both the "optional parameter" and the "optionalattribute" and "defaparameparametervalueattribute" are directly used, the compiled results are the same, and the reflector cannot determine whether to use the "optional parameter" or "optionalattribute ".Source codeWhich one is used in, the simply assumption is the second.
Suspect
Although optionalattribute does not appear in the C # code of testmethod, it does not exist in the compiled Il and metadata, but it still appears in the typerefs of the compiled assembly, defaultvalueattribute does not appear. Why?
Reference
On msdn:
Http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/d1be12e0-6325-427a-8e25-02fbd8396b1b/#18b08278-28a9-43dc-b3d4-e4694ca0260d
Http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/31731806-dd83-4483-89b4-30001af14ab7/#352d019c-950c-42de-88f6-b0fecdf34351
Http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/86f6d205-21b8-45e3-b5ec-3e9d5c1f9feb/
On stackoverflow:
Http://stackoverflow.com/questions/5456989/is-the-new-feature-of-c-4-0-optional-parameters-cls-compliant
Http://stackoverflow.com/questions/5497514/what-does-opt-mean-in-msil
Http://stackoverflow.com/questions/5522438/why-does-a-custom-attribute-appear-both-in-il-and-metadata