I'm targeting .NET 3.5 SP1 and I'm using CommentChecker to validate my XML documentation, everything works OK until I get to a class like this:
/// <summary>
/// documentation
/// </summary>
public sealed class MyClass {
/// <summary>
/// documentation
/// </summary>
public void Method() {
}
}
In the example above, as I understand, the compiler generates a default constructor for my class. The problem with this is that CommentChecker generates warnings telling me that the constructor is missing the comments.
I tried to modify the program to detect this special case and ignore it but I'm stuck, I already tried with IsDefined(typeof(CompilerGeneratedAttribute), true)
but that did not work.
So in short, how can I detect the default constructor using reflection?
There is no way to detect automatically generated default constructors through metadata. You can test this by creating a class library with two classes, one with an explicit default constructor, and one without. Then run ildasm on the assembly: the metadata of the two constructors is identical.
Rather than try to detect generated constructors, I would simply change the program to allow missing documentation on any default constructor. Most documentation generation programs, like NDoc and SandcastleGUI, have an option to add standard documentation to all default constructors; so it's really not necessary to document them at all. If you have an explicit default constructor in your code, you can put three slashes (///) above the constructor - nothing else - to disable the Visual Studio warning about missing documentation.
If you're willing to dig a little into the IL, then you can get most of the way there.
First, assuming you have
ConstructorInfo
instance which you know to be parameterless, you can get the method body and the bytes for the method body like so (we'll start building an extension method to do this):You can reject any method bodies don't have seven bytes.
The reason will be obvious in the code that follows.
In section I.8.9.6.6 of ECMA-335 (Common Language Infrastructure (CLI) Partitions I to VI), it states CLS rule 21:
This means that before anything else is done, the a base constructor must be called. We can check for this in the IL. The IL for this would look like this (I've put the byte values in parenthesis before the IL command):
We can now start checking the bytes for this:
Now comes the metadata token. The
call
instruction requires a method descriptor to be passed in the form of a metadata token along with the constructor. This is a four-byte value which is exposed through theMetadataToken
property on theMemberInfo
class (from whichConstructorInfo
derives).We could check to see that the metadata token was valid, but because we've already checked the length of byte array for the method body (at seven bytes), and we know that there's only one byte left to check (first two op codes + four byte metadata token = six bytes), we don't have to check to see that it's to a parameterless constructor; if there were parameters, there would be other op codes to push the parameters on the stack, expanding the byte array.
Finally, if nothing else is done in the constructor (indicating that the compiler generated a constructor that does nothing but call the base), a
ret
instruction would emitted after the call the metadata token:Which we can check like so:
It needs to be noted why the method is called
MightBeCSharpCompilerGenerated
, with an emphasis on Might.Let's say you have the following classes:
When compiling without optimizations (typically
DEBUG
mode), the C# compiler will insert a fewnop
codes (presumably to assist the debugger) for theDerived
class which would cause a call toMightBeCSharpCompilerGenerated
to return false.However, when optimizations are turned on (typically,
RELEASE
mode), the C# compiler will emit the seven-byte method body without thenop
opcodes, so it will look likeDerived
has a compiler-generated constructor, even though it does not.This is why the method is named
Might
instead ofIs
orHas
; it indicates that there might be a method that you need to look at, but can't say for sure. In other words, you'll never get a false negative but you still have to investigate if you get a positive result.The following code will return information on any parameterless constructors in your type:
I do not know of a way of differentiating between a default constructor and an explicitly specified parameterless constructor.
A possible workaround for your CommentChecker issue would be to explicitly create the parameterless constructor where one is required and comment it appropriately.