Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>If you're willing to dig a little into the IL, then you can get <em>most</em> of the way there.</p> <p>First, assuming you have <a href="http://msdn.microsoft.com/en-us/library/system.reflection.constructorinfo.aspx" rel="nofollow"><code>ConstructorInfo</code></a> 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):</p> <pre><code>public static bool MightBeCSharpCompilerGenerated( this ConstructorInfo constructor) { // Validate parmaeters. if (constructor == null) throw new ArgumentNullException("constructor"); // If the method is static, throw an exception. if (constructor.IsStatic) throw new ArgumentException("The constructor parameter must be an " + "instance constructor.", "constructor"); // Get the body. byte[] body = constructor.GetMethodBody().GetILAsByteArray(); </code></pre> <p>You can reject any method bodies don't have seven bytes.</p> <pre><code> // Feel free to put this in a constant. if (body.Length != 7) return false; </code></pre> <p>The reason will be obvious in the code that follows.</p> <p>In section I.8.9.6.6 of <a href="http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf" rel="nofollow">ECMA-335 (Common Language Infrastructure (CLI) Partitions I to VI)</a>, it states CLS rule 21:</p> <blockquote> <p>CLS Rule 21: An object constructor shall call some instance constructor of its base class before any access occurs to inherited instance data. (This does not apply to value types, which need not have constructors.)</p> </blockquote> <p>This means that before <em>anything</em> else is done, the a base constructor <em>must</em> 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):</p> <pre><code>// Loads "this" on the stack, as the first argument on an instance // method is always "this". (0x02) ldarg.0 // No parameters are loaded, but metadata token will be explained. (0x28) call &lt;metadata token&gt; </code></pre> <p>We can now start checking the bytes for this:</p> <pre><code> // Check the first two bytes, if they are not the loading of // the first argument and then a call, it's not // a call to a constructor. if (body[0] != 0x02 || body[1] != 0x28) return false; </code></pre> <p>Now comes the metadata token. The <a href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.call%28v=vs.95%29.aspx" rel="nofollow"><code>call</code> instruction</a> 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 the <a href="http://msdn.microsoft.com/en-us/library/system.reflection.memberinfo.metadatatoken" rel="nofollow"><code>MetadataToken</code> property</a> on the <a href="http://msdn.microsoft.com/en-us/library/8fek28hz" rel="nofollow"><code>MemberInfo</code> class</a> (from which <code>ConstructorInfo</code> derives).</p> <p>We <em>could</em> 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.</p> <p>Finally, if <em>nothing else is done</em> in the constructor (indicating that the compiler generated a constructor that does nothing but call the base), a <code>ret</code> instruction would emitted after the call the metadata token:</p> <pre><code>(0x2A) ret </code></pre> <p>Which we can check like so:</p> <pre><code> return body[6] == 0x2a; } </code></pre> <p>It needs to be noted why the method is called <code>MightBeCSharpCompilerGenerated</code>, with an emphasis on <strong>Might</strong>.</p> <p>Let's say you have the following classes:</p> <pre><code>public class Base { } public class Derived : Base { public Derived() { } } </code></pre> <p>When compiling without optimizations (typically <code>DEBUG</code> mode), the C# compiler will insert a few <a href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.nop%28v=vs.95%29" rel="nofollow"><code>nop</code> codes</a> (presumably to assist the debugger) for the <code>Derived</code> class which would cause a call to <code>MightBeCSharpCompilerGenerated</code> to return false.</p> <p>However, when optimizations are turned on (typically, <code>RELEASE</code> mode), the C# compiler will emit the seven-byte method body without the <code>nop</code> opcodes, so it will look like <code>Derived</code> has a compiler-generated constructor, even though it does not.</p> <p>This is why the method is named <code>Might</code> instead of <code>Is</code> or <code>Has</code>; it indicates that there <em>might</em> 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.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload