Scan for Specific Parameter Types Within an Assembly’s Types’ Methods in C#

Ever wondered how many methods with a specific signature are contained within an assembly ? Say, how many methods that take at least one parameter of type object are in all the types marked as public within the mscorlib.dll assembly.

(TL;DR: I’m in a real hurry, and need the code to do this really quick ! Where is it ? You’ll find it towards the end of the article, here)

mscorlib.dll is a somewhat challenging example, since this assembly contains all the core types (Byte, Int32, String, etc) plus many more, each with scores of methods. In fact, the types within are so frequently used that the C# compiler will automatically reference this assembly when building your app, unless specifically instructed not to do so (via the /nostdlib switch).

Browsing Assemblies

The first thing in our quest for finding specific method signatures is to go get a list of methods. This – in turn – requires obtaining the list of types that actually contain all those methods.

An IL (Intermediate Language) disassembler will usually do the job of showing the types contained within an assembly as well as their methods. 2 such programs are Microsoft’s own Ildasm.exe and the open-source ILSpy.

Let’s use ILSpy to take a quick look at mscorlib.dll:

Figure 1 – ILSpy in action

One can clearly see the methods and their signatures and drill down at leisure, but our original quest was to find only those that match a particular signature. Though a Search function exists in ILSpy, including regex capability, I couldn’t find a way of getting it to do just that.

Mirror, mirror on the wall

What we can do is turn to a programmatic way of achieving our desired result. After all, .NET has a mechanism called reflection, which allows going over the metadata included in the assemblies themselves, thus obtaining detailed information about the types within, including the parameters of any methods defined.

In effect, reflection defines specific types that can be used to go over the assemblies’ metadata. 3 such types are Type, MethodInfo and ParameterInfo, and they are all we need to search through all the method signatures.

Simply create a Console App (.NET Framework) project and ensure that the Main method has the content below. To make things a bit more interesting, we’re skipping all the Equals methods. Our original requirement that at least one parameter of the method is of type object stays in place however:

        static void Main(string[] args)
        {
            Assembly currentAssembly = Assembly.Load("mscorlib.dll");

            int noMethodsEncountered = 0;
            int noHits = 0;
            foreach (Type myType in currentAssembly.ExportedTypes)
            {
                MethodInfo[] allMethods = myType.GetMethods();
                foreach (MethodInfo mi in allMethods)
                {
                    noMethodsEncountered++;
                    if (mi.Name != "Equals")
                    {
                        foreach (ParameterInfo pi in mi.GetParameters())
                        {
                            if (pi.ParameterType.FullName != null && pi.ParameterType.FullName.Equals("System.Object"))
                            {
                                Console.WriteLine("Hit in type: {0} for method: {1}", myType.ToString(), mi.ToString());
                                noHits++;
                                break;      // break at the first occurrence as to not get multiple incorrect hits
                            }
                        }
                    }
                }
            }

            Console.WriteLine("Finished searching through {0} methods in all types", noMethodsEncountered);
            Console.WriteLine("Found {0} hits", noHits);
            Console.ReadLine();
        }

The last page of the output is listed below. For every “hit” the respective method signature is printed, preceded by the type it’s a member of.

Figure 2 – Lots of methods encountered

Now let’s suppose we’d only like to see public methods that meet our previous criteria. So let’s simply add an extra condition against the MethodInfo type we’re using, then run the code again.

               if (mi.Name != "Equals" && mi.IsPublic)
Figure 3 – Same number of methods encountered (deja-vu ?)

Still 1163 hits ? There’s really no private method that takes at least one object parameter in the types defined as public in the whole mscorlib.dll ? That’s rather odd.

In fact, it’s quite easy to find one such private method. System.Enum‘s InternalCompareTo is just one of the several such methods contained in that namespace. What gives ?

The Fine Print

There seems to be something wrong with the GetMethods method on line 9 we’re invoking for each type. Quickly turning to the Microsoft page that describes it in detail yields that there are in fact 2 overloads. The one we’re calling – with no parameters – actually “returns all the public methods of the current Type“. So the list of methods we started with was actually only containing the public ones.

In our original search we wanted all methods, not just the public ones. Luckily the other overload for GetMethods allows defining in detail the kind of methods we’re after, by specifying a combination of flags found in the BindingFlags enum.

At first glance, passing a bitwise combination of BindingFlags.Public and BindingFlags.NonPublic looks to be enough, but one needs to take heed of the note specified on Microsoft’s page describing the enum:

You must specify Instance or Static along with Public or NonPublic or no members will be returned.

So our bitwise combination requires 4 values, and the original code gets modified as such:

            MethodInfo[] allMethods = myType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);

Let’s run the code again and see the difference:

Figure 4 – Lots and lots of methods encountered

There’s quite an increase in the numbers as opposed to what we got originally in figure 2. The numbers have almost doubled by returning the non-public methods.

You’d think we’re done at this point, but:

Constructors are not included in the array of methods returned by this call. Make a separate call to GetConstructors() to get the constructor methods.

Under Construction

The GetConstructors method follows a similar pattern to the GetMethods – the same 2 overloads and the same discussion around the use of BindingFlags. What changes is that a ConstructorInfo array is returned. Therefore we have to consider and iterate through both the methods as well as the constructors for each type.

Both MethodInfo and ConstructorInfo derive from MethodBase, so let’s simply put together the methods and the constructors by using Concat, and redefine the MethodInfo iterator we previously used as a MethodBase variable. Our code thus becomes:

    static void Main(string[] args)
    {
        Assembly currentAssembly = Assembly.Load("mscorlib.dll");

        int noMethodsEncountered = 0;
        int noHits = 0;
        foreach (Type myType in currentAssembly.ExportedTypes)
        {

            MethodInfo[] allMethods = myType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
            ConstructorInfo[] allConstructors = myType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
            foreach (MethodBase mi in allMethods.Concat((MethodBase[])allConstructors))
            {
                noMethodsEncountered++;
                if (mi.Name != "Equals")
                {
                    foreach (ParameterInfo pi in mi.GetParameters())
                    {
                        if (pi.ParameterType.FullName != null && pi.ParameterType.FullName.Equals("System.Object"))
                        {
                            Console.WriteLine("Hit in type: {0} for method: {1}", myType.ToString(), mi.ToString());
                            noHits++;
                            break;      // break at the first occurrence as to not get multiple incorrect hits
                        }
                    }
                }
            }
        }

        Console.WriteLine("Finished searching through {0} methods in all types", noMethodsEncountered);
        Console.WriteLine("Found {0} hits", noHits);
        Console.ReadLine();
    }

Now the complete picture becomes available:

Figure 5 – Both regular methods and constructors counted

Sign On The Dotted Line

One more thing before wrapping up. The signature of the method – in the strict sense of the word – doesn’t contain the return type in C#. Neither does it contain the name of the parameters. Eric Lippert’s answer here explains it very well, as does this MS article.

If you take a quick look at Figure 1, you’ll see that ILSpy actually shows the signature – according to definition – for every method, followed by a double colon and the return type.

Q & A

Q: Where can I find the meaning of the various icons used in ILSpy ? I’ve checked the list of the Visual Studio ones, but they don’t really match.
A: The icons themselves are the same ones used in SharpDevelop IDE, as can be seen at this time (May 2019) from the respective folder within ILSpy’s git repository. One can simply clone the repo and use Windows Explorer to see them – the relevant list of icons is below. Note that some of the icons you’re seeing in ILSpy are the result of mixing 2 icons, eg the icon for an internal constructor would be the result of the constructor icon on which the internal overlay icon was superimposed.

Q: Why would you want to see how many signatures of a specific type are there ?
A: It comes handy when one looks closer at boxing, which happens when a value type is automatically converted to a reference one and stored on the heap. Supplying a value type to a function that takes an object parameter is one way of triggering it.

Q: Can ILSpy be used against a .netmodule file ?
A: Certainly. Of course, there won’t be any assembly manifest, but the types and IL within the module will be readily available. For more details about multi-file assemblies and .netmodule files, see Jeffrey Richter’s “CLR via C#”.

Q: Ildasm groups everything within System, however ILSpy has multiple System.* entries at the root level. Why is there this difference in output ?
A: Indeed, Ildasm groups namespaces hierarchically under System, while ILSpy puts each namespace at the root level. Why is this so ? I’m guessing each app’s developers saw the advantages of their method as bigger than the others.

Q: The JetBrains .NET Decompiler here states that it can parse Windows metadata files (.winmd). What are these ? I thought only .dll/.exe/.netmodule were output of compiled code in .NET.
A: They belong to the Windows Runtime world. They are similar in format with the .NET assembly files, as documented here.

Q: What’s the point of loading the mscorlib.dll assembly manually, when the application’s module will load it anyway, based on the fact that you’re referring at least one of the base types, therefore the JIT will trigger it to load anyway ?
A: I just need an Assembly object to be created based on the data found inside mscorlib.dll. As per Jeffrey Richter’s “CLR via C#“: “This assembly is automatically loaded when the CLR initializes, and all AppDomains share the types in this assembly”. So it gets loaded way before the first line of our code gets encountered. An alternate way of getting the Assembly variable we’re after is to simply use the current’s AppDomain list of loaded assemblies. In my tests, mscorlib.dll was always the first assembly loaded, hence the [0] hack below. Needless to say that the said hack is not the healthy way to go about it (order of assemblies could be different/might change). The 3 lines below would replace the 3rd line in any of the code snippets in the post:

        AppDomain currentAppDomain = Thread.GetDomain();
        Assembly[] loadedAssemblies = currentAppDomain.GetAssemblies();
        Assembly currentAssembly = loadedAssemblies[0];

Q: I’ve been using VMMap to look at how the mscorlib.dll assembly gets loaded once the app starts, but I’m not really finding it in the list of images loaded. There’s a mscorlib.ni.dll loaded indeed as soon as the app starts, but how about the other one ?
A: This is indeed interesting and I’m looking into it. Will update when I get new info.

Q: I’ve looked with ILSpy under the System.IO namespace, but not all the types seem to be there. For instance, I can’t find type System.IO.FileSystemWatcher. Why is it so ?
A: As per “CLR via C#“:

[…]the various types belonging to a single namespace might be implemented in multiple assemblies. For example, the System.IO.FileStream type is implemented in the MSCorLib.dll assembly, and the System.IO.FileSystemWatcher type is implemented in the System.dll assembly.

CLR via C# – Chapter 4

If you’re looking for a particular type, you can simply start from the Microsoft documentation. You’ll find the namespace where it’s located, and the assembly (or assemblies) that implement it:

The System.dll is used when working against the .NET Framework, the netstandard.dll used when working against .NET Standard, and System.IO.FileSystem.Watcher.dll is essentially an empty assembly that contains no code, but points to the System one within its manifest. Why are there 3 files, when the quoted text above said that there should be only 1 ? I’m speculating it’s because the other 2 files weren’t around back when the book was written (2012).

Q: Couldn’t the reflection-only load context be used as well, just as specified here ?
A: Indeed, the reflection-only context can be used to load the assembly, since we’re not invoking any method (an exception is thrown if one attempts to). In the code snippets above, the 3rd line needs to be modified as below. If you run it, you’ll get the same numbers.

 Assembly currentAssembly = Assembly.ReflectionOnlyLoad("mscorlib.dll");

Q: I don’t see the accessibility modifiers (public /private/protected/protected internal/etc.) for the method signatures shown in the output. Shouldn’t they be there as well ?
A: We weren’t after this one directly, so they weren’t obtained. But if you want to list them, the information is visible within the MethodInfo/MethodBase class. From the documentation: ” You can determine the method’s visibility by retrieving the values of the IsFamilyAndAssemblyIsFamilyOrAssemblyIsPrivate, and IsPublic properties“. The Attributes property will also return this plus additional info (eg if the method is static). Just be aware of the difference in naming between the CLR terms and the C# ones, eg. private is the same in both worlds, but CLR’s family is C#’s protected. Table 6-1 in the “CLR via C#” shows the complete list.

Q: Any particular reason why you filtered out Equals methods in your sample ?
A: It turns out there are 1359Equals methods within mscorlib.dll at this time. The high number is most likely explained by the fact that the virtual method defined in System.Object merely checks if the 2 objects involved (this and the argument to Equals) refer to the same exact object, but will always return false for 2 distinct objects that contain the same data. Thus the need to override for most of the types.

Q: On line 7 you’re using ExportedTypes to get all the types defined within the assembly. Why not use DefinedTypes ?
A: We were after the methods defined in types marked as public in the assembly. As per the Microsoft doc, ExportedTypesgets a collection of the public types defined in this assembly that are visible outside the assembly“. DefinedTypes on the other hand will also include types defined as internal in the assembly, so essentially return all the types defined (a type can be either defined as either public or internal within an assembly).

Q: I was trying to make the last version of your code more easy to read, by using the lines below. It compiles fine, but when I run this I get “System.InvalidCastException: ‘Unable to cast object of type ‘d__59`1[System.Reflection.MethodBase]’ to type ‘System.Reflection.MethodBase[]’.’“. Why is this so ?

            MethodBase[] convertedAllMethods = (MethodBase[])allMethods;
            MethodBase[] convertedAllConstructors = (MethodBase[])allConstructors;
            MethodBase[] allMethodsAndConstructors = (MethodBase[])convertedAllMethods.Concat(convertedAllConstructors);
            foreach (MethodBase mi in allMethodsAndConstructors)

A: I’m not entirely sure why, but if you use the form below to define the variable that will hold all methods and constructors, it will work as expected:

            IEnumerable<MethodBase> allMethodsAndConstructors = convertedAllMethods.Concat(convertedAllConstructors);

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s