Girish Jain on .Net Framework

I will be writing about my favorite technology which is Microsoft .Net Framework and how to use it to improve developer productivity

LINQ Query - INNER JOIN with GROUP

clock October 3, 2012 22:39 by author Girish Jain

Recently, while working on a project, I came across a certain requirement to use LINQ and get data from two different sources and at the same time I had to use inner join and group the data. It took a while for me to figure out how best to write the query as I could either do inner join or group but was not able to achieve both in the same query. Finally, I figured out the procedural approach to get it done therefore, I thought of sharing the solution with you all on blog assuming it might be helpful to you. For the sake of simplicity, I am presenting the same challenge but with very simple and common real world entities such employee and department example.

Given two lists, one for departments and another for employees, we need to get the listing of all departments along with employee having least salary for that department. For example, find below listing of departments and employees:

ID Name
1 IT
2 Finance


Name DepartmentID Address Salary
Ram 2 Parel 2500
Shyam 2 Borivali 1500
Sita 2 Panvel 2000
Gita 1 Virar 1600
Kishan 1 Vikroli 1400
Kanhaiya 1 Surat 1300


Now we want to get the highlighted rows as output, i.e. departments listing along with employee with minimum salary for that department.

I have initialized these two data sources in code as shown below:

private List<Department> _deptartments = new List<Department>()
				{
					new Department{ ID=1, Name="IT" },
					new Department{ ID=2, Name="Finance" }
				};

private List<Employee> _employees = new List<Employee>()
				{
					new Employee{ Name="Ram", Address="Parel", Salary=2500, DepartmentID=2 },
					new Employee{ Name="Shyam", Address="Borivali", Salary=1500, DepartmentID=2 },
					new Employee{ Name="Gita", Address="Panvel", Salary=2000, DepartmentID=2 },
					new Employee{ Name="Sita", Address="Virar", Salary=1600, DepartmentID=1 },
					new Employee{ Name="Kishan", Address="Vikroli", Salary=1400, DepartmentID=1 },
					new Employee{ Name="Kanhaiya", Address="Surat", Salary=1300, DepartmentID=1 }
				};

With LINQ, there are two approaches possible, procedural approach or using new keywords for LINQ. Find below the procedural code to get the desired output using GroupJoin method:

var list = _deptartments.GroupJoin(_employees,
	Dep => Dep.ID,
	Emp => Emp.DepartmentID,
	(dep, empList) =>
		new
		{
			DepartmentName = dep.Name,
			MinSalary = empList.Min(x => x.Salary),
			EmployeeName = (from e in empList 
                    where e.Salary == empList.Min(x => x.Salary) 
                    select e.Name).FirstOrDefault()
		}
	);


Thanks to my colleague Aditya Dhiwar who showed me how to achieve the same using LINQ keywords in C#. Find below code for same:

var list = from e in _employees
            group e by e.DepartmentID into dptgrp
            join d in _deptartments
            on dptgrp.Key equals d.ID
            let minsal = dptgrp.Min(x => x.Salary)
            let employee = dptgrp.Where(e => e.Salary == minsal)
            from e in employee 
            select new
            {
              EmployeeName = e.Name,
              DepartmentName = d.Name,
              MinSalary = minsal,
            }

Find below the complete source code:


using System;
using System.Collections.Generic;
using System.Linq;

public class Employee
{
	public string Name { get; set; }
	public string Address { get; set; }
	public decimal Salary { get; set; } 
	public int DepartmentID { get; set; } 
}

public class Department
{
	public int ID  { get; set; }
	public string Name  { get; set; }
	public Employee Manager { get; set; }
}

public class LINQTest
{
	private List<Department> _deptartments = new List<Department>()
					{
						new Department{ ID=1, Name="IT" },
						new Department{ ID=2, Name="Finance" }
					};

	private List<Employee> _employees = new List<Employee>()
					{
						new Employee{ Name="Ram", Address="Parel", Salary=2500, DepartmentID=2 },
						new Employee{ Name="Shyam", Address="Borivali", Salary=1500, DepartmentID=2 },
						new Employee{ Name="Gita", Address="Panvel", Salary=2000, DepartmentID=2 },
						new Employee{ Name="Sita", Address="Virar", Salary=1600, DepartmentID=1 },
						new Employee{ Name="Kishan", Address="Vikroli", Salary=1400, DepartmentID=1 },
						new Employee{ Name="Kanhaiya", Address="Surat", Salary=1300, DepartmentID=1 }
					};

	public void GetEmployeesWithMinSalaryInDepartment()
	{
        // Get listing of all departments along with the employee who is
        // paid least salary for that department

        var list = _deptartments.GroupJoin(_employees,
	        Dep => Dep.ID,
	        Emp => Emp.DepartmentID,
	        (dep, empList) =>
		        new
		        {
			        DepartmentName = dep.Name,
			        MinSalary = empList.Min(x => x.Salary),
			        EmployeeName = (from e in empList 
                        where e.Salary == empList.Min(x => x.Salary) 
                        select e.Name).FirstOrDefault()
		        }
	        );

		foreach (var d in list)
		{
			Console.WriteLine(d.DepartmentName);
			Console.WriteLine(d.EmployeeName);
			Console.WriteLine(d.MinSalary);
		}
	}
}

Hope you find it useful. Happy Coding!!

Vande Mataram!

(A salute to motherland)

P.S. In addition to blogging, I use Twitter to share tips, links, etc. My Twitter handle is: @girishjjain




Assembly Identity and GAC

clock September 20, 2012 16:34 by author Girish Jain

Assembly Identity and GAC

Today, I am going to draw your attention to a very small detail in .Net Framework - Global Assembly Cache (GAC). GAC is the machine-wide store for .Net framework assemblies. Until recently, I have been under the impression that assemblies in GAC are identified based on following four characteristics:

1. Assembly Friendly Name

2. Assembly Version Number

3. Assembly Culture

4. Public Key Token

As far as I remember, I read it in the book so either I read it wrong or the book is wrong. In any case, lets clear up the understanding.

My understanding was that combination of these four attributes will always be unique in GAC. I lived under this understanding for a long while until recently when I noticed few assemblies in GAC where all above four attributes of a certain assembly were same. Shock! First because my understanding is proved to be incorrect and second it means I will have to go into detail to identify the attributes which uniquely identify assemblies in GAC and correct my understanding.

Let’s start with the discovery. I observed that when you open GAC folder (C:\Windows\assembly) in Windows Explorer there is an extra column shown, called Processor Architecture. I noticed that there is a difference in the processor architecture of the assemblies where all other four attributer were common. It led me to the conclusion that the assemblies share same friendly name, culture, version number, and public key token BUT they are targeting different processor architecture. I decided to try out this by creating a basic assembly and installing it to GAC with keeping all four attributes of the assembly same but all targeting different processor architecture - Any CPU, x86, and x64 (options in Visual Studio).

So I created a simple DLL with one class and a method with stereo code (am sure you will find code very familiar :-):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GACLearning
{
    public class Class1
    {
        public static string HelloWorld()
        {
            return "Hello World";
        }
    }
}

Built the project successfully and now it was time to generate the DLL by targeting different platforms. You can change the target processor architecture (platform) for your assembly in Visual Studio using project property page as follows (it provides three options - Any CPU, x86, and x64) :

Since we need to install assembly to GAC, it needed to be strong named. Hence, sign the assembly on signing tab under project propert page as follows:

For detailed steps to generate the key file and how to re-sign the assembly just before deployment using sn.exe tool, refer MSDN.

[Note: I removed verification of the assembly using -Vr option of sn.exe tool to make it easy for me to install assemblies to GAC]

Now I built solution three time and each time selected a different platform target under project property page and copied generated DLL to a separate folder. Three DLLs are ready which have same name, version, culture, and public key token but each target a different platform. I am ready for my test! I just dragged and dropped these assemblies to GAC folder and they all got installed to GAC happily. All three living in GAC at the same time. Shell extension does the trick under the hood by calling gacutil.exe and installs the assemblies to GAC or you can do so manually as well. Refer below screenshot of all three assemblies in GAC with only difference being in target platform:


Conclusion

There are five attributes which uniquely identify an assembly or form the identity of the assembly:

1. Assembly Friendly Name

2. Assembly Version Number

3. Assembly Culture

4. Public Key Token

5. AND Processor Architecture (Target Platform)

On a side note, if you open GAC folder directly in command prompt you will see the assembly being installed into respective folder for each platform, as shown below:

GAC 32-bit folder:

GAC 64-bit folder:

GAC MSIL folder:

Happy Coding!!

Vande Mataram!

(A salute to motherland)

P.S. In addition to blogging, I use Twitter to share tips, links, etc. My Twitter handle is: @girishjjain




Assembly Comparer Tool

clock August 14, 2012 11:37 by author Girish Jain

Install Girish Jain's Assembly Comparer (0.5 MB)

Download Sample Assembly for Comparison (13 KB)

Overview

Often we need to compare two different versions of an assembly and see differences between the two. This is especially true when you are making a point release (or hot fix) and not upgrading the entire environment and you want to compare the selected assemblies that you will overwrite in production (live) environment, so that you can be sure of changes which are going in the new assembly.

CLR assemblies being binary files cannot be compared with usual text file comparer tools. Hence, you need a separate tool which can compare CLR assemblies. That’s where I welcome you to using this new tool, Girish Jain’s Assembly Comparer.

Using this tool, you can compare two CLR assemblies and it will give you nice graphical view to see the differences along with advanced comparer features to filter view for mismatch items only or common items, and so on. Best way to explain its features would be by doing a comparison and showing differences hence, I have created a fiction data layer DLL with some common entities and data access layer classes/methods and created two versions of it v1.1 and v1.2.

Find below screenshot for when I load both assemblies for comparison in the tool:

Assembly Comparer Default View

This is the default comparison view and it shows all the members of an assembly in a hierarchical tree view for both the assemblies, such as:

1. All external references

2. All modules

3. Types within each module

4. All members of each type such as fields, properties, methods, events, etc.

5. IL instructions within each method

Each object has an icon associated with and it is the standard icon that you see in Visual Studio object browser for a given object. The best part of these icons is that they have a background color too which tells whether the given object is COMMON between both assemblies (in which case the background color would be green) OR the object does not exist in the other assembly (in which case the background color will be red).

Mismatch Items

You can filter the view for Mismatch Items using either toolbar or View  ONLY Mismatch Items menu option. This view will show just the differences between both the assemblies at a very granular level i.e. till method IL instructions level. Here, you will see certain objects with green background while other with red background, refer below screenshot. In a mismatch view, why are there objects with green background? The answer is very simple, a given object may be common between two assemblies (and hence, its icon appears with green background) but its members may not necessarily be common such as method body, properties additions, or data type changes, etc.

Mismatch Items View

Differences in Detail

You can expand a type to see all the members which are not COMMON (or same) between two assemblies in Mismatch Items view. Refer below screenshot for the differences explained in detail:

Differences Explained 1

Method Instructions Difference

As you can see in below screenshot, the method’s icon is appearing with green background which indicates that method signature matches between two assemblies. You need to expand method node to see actual IL instruction level differences, as show below:

Differences explained 2

You can download the application from here. Feel free to use it.

Happy Coding!!

Vande Mataram!

(A salute to motherland)

P.S. In addition to blogging, I use Twitter to share tips, links, etc. My Twitter handle is: @girishjjain




Investment Calculator App for Windows Phone 7.5

clock August 4, 2012 00:44 by author Girish Jain

Dear reader, today I am not going to write about .Net or its related technology but my own personal piece of work for Windows Phone 7.5 (WP) platform. So, read no further if you don’t use a windows phone.

I have developed an application for WP platform which has been published to marketplace and this post explains all the features of the app at length. This application is for a common man and not only for professionals working in financial district. This does not speak jargons but simple terms and will be helpful in making regular investment decisions, simple & compound interest calculations, understanding inflation effect, etc. Let’s go over key screens and functionality of application.

Main Menu

Find below the main menu page of the app, which provides access to all the functionality:

 

Future Value (One-time investment)

Ever wondered what would be the future value or effective post-tax and post-inflation return for a fixed-rate investment that you were planning to make? You need to use future value screen in this app. This is a very powerful tool for savers who invest their savings in various fixed rate instruments such as fixed deposits, bonds, debentures, etc. This tool lets you calculate what would be your post-tax and post-inflation return for a fixed-rate investment. It gives you multiple options for compounding such as monthly, quarterly, half-yearly, and yearly. It also allows you to choose whether you re-invest interest earned at the end of each period or withdraw it. Refer below screenshot:

Below screen show the future value calculation report with post-tax and post-inflation return:

 

Future Value (Periodic investment)

Another version of future value calculator but this one assumes that certain amount is invested periodically (regularly over a period) and hence the name periodic investment. This tool is suitable for scenarios where you invest same amount regularly in a fixed rate saving instrument, for example, your monthly/yearly contribution to provident fund or recurring deposit.

 

Personal Accounts

You want to record money you lent to your friends/customers and keep track of it? You need to use Personal accounts functionality. This screen lets you record your cash transactions so that you can track your money by creating individual accounts and then calculate interest for the same. It lets you create accounts and record entries for the same as below:

You can add entries to account using the below screen:

Below is the account statement for view purpose:

 

Interest Calculator

You wanted to calculate interest on your current account or for your business you wanted to calculate interest that you need to charge your customers and need a quick and easy way to do it on the go? You need to use interest calculator tool and you can calculate simple and compound interest both for an account (created using personal accounts screen). It provides you detailed interest calculation report as follows:

Simple interest calculation:

Compound Interest Calculation:

 

Inflation Calculator

Ever wondered what would be the value (purchasing power) of money when you retire? Or how much saving you need to make to be able to live a comfortable retirement life? You need this tool to help you with determining future value of money after adjusting for assumed inflation rate or to find out how much you need to have in today’s value (present value) for a sum in future, refer below screenshot:

 

Loan Calculator

It’s a handy tool on your smart phone to quickly check EMI amount for a loan or compute loan amount for your desired EMI as follows:

 

Summary

In summary, it provides an easy-to-use interface for some of the regular calculations that you need to do on a day-to-day basis or in the process of making a financial decision and plan for your future such as determining future value, loan/mortgage calculation, simple & compound interest calculation, understanding inflation effect, etc.

Lastly, one of the best features of this app, which makes it easy for users to get the most out of it, is its help screen for all screens/tools which explains in detail the functionality of screen/tool, each input field, output of it, etc. For example:

THE BEST OF ALL, this application is available in Trial mode too, so you can download it on your phone, try out ALL the features and then make your decision!



Tracing with Code Injection Part II

clock April 27, 2012 06:41 by author Girish Jain

Download source code - Advanced Tracing with Code Injection Part II.zip (171 kb)

Overview

Welcome to part II of this blog post series where I show you how to inject code into an assembly to trace method execution at runtime, along with its parameter values and, all of this being achieved without writing any code in your method’s body.

Before you read further, I would suggest you read first blog post to get fair idea of code injection approach developed in earlier post and how tracing works. In the first post, I have created an application that uses Mono.Cecil library to inject code into any CLR assembly (DLL or EXE). The application will inject code to all methods marked with a known attribute in the given assembly.

Objective of this blog post series is to develop an automated solution (using code injection) which logs certain key information from a running application, of course, only when tracing is turned on for the application (which is called as runtime instrumentation). Now, I want to achieve this without manually writing tracing code for the same. CLR tracing framework is great as you can turn tracing on/off using application’s configuration file, and when turned on you can further control the type of information (such as verbose, information, warning, or error), which gets logged to trace listener, using trace switch. So the key objective is to develop a code injection solution which injects tracing code to a given assembly which will log method signature and its parameters values each time the method executes.

Advantages

1. Code injection approach frees developer from manually writing tracing code at the start of their method body.

2. Information logged during runtime would be a great help when diagnosing a production environment issue, especially for a large distributed application as it will tell you the sequence in which methods executed, and it would be helpful during debugging in development phase as well.

Done so far

The first post has created an application which logs method signature to trace listener each time the method executes. Please note that tracing code is injected only to methods marked with a custom attribute. Now, all that you had to do to achieve this functionality in your own application was to follow these simple steps:

1. Add reference to AdvancedTracing library in your project (a light-weight library which defines custom attribute you need to apply to your methods and a custom database trace listener)

2. Mark methods with the custom attribute [LogMethodExecutionEntryAttribute]

3. Add a post-build event to project to invoke CodeInjection.exe (or you can use any other automated mechanism such as a .bat file to pass project's output, DLL/EXE, to CodeInjection.exe application to inject tracing code).

4. Add a trace listener to application configuration file

Voila! All methods marked with the custom attribute will start logging method signature to trace destination each time the method executes, as follows:

16/04/2012 11:20:03 : System.Int32 InjectedCalc::Add(System.Int32,System.Int32) invoked 

What Next?

Sounds good so far but, just the method signature would not be end of the world, let’s see if we can get more. What if we get method parameters values being logged as well to trace listener? Just the method signature being logged will not be very helpful but the data passed to method would be of great help while diagnosing any issue. With the same objective in mind, I started modifying CodeInjection.exe application to inject code to log method parameters data as well, along with method signature. So for example, for a simple Add method, such as:

[LogMethodExecutionEntry(true)] 
static public int Add(int i1, int i2) 
{ 
    return i1 + i2; 
} 

I want to see the values passed to its parameters being logged as well, as follows:

16/04/2012 11:23:10 : System.Int32 InjectedCalc::Add(System.Int32,System.Int32) invoked with data:10,20,

With this objective, I modified the original logic as follows: (sub-points 6.x are new changes)

1. Load the input assembly

2. Loop over all its modules

3. For each module, loop over all its types

4. For each type, loop over all its methods

5. For each method, check whether it is marked with LogMethodExecutionEntryAttribute custom attribute

6. If method is marked with the known custom attribute, then inject following IL instructions at the start of method body

a. If method has any parameters defined, then load an array of type object to stack, with array’s size being equal to number of method parameters using newarr instruction. Now loop over all the parameters and inject code as below:

i. Load (push) each argument value using ldarg instruction

ii. If the parameter is passed by reference, then use ldind instruction to de-reference the pointer and push actual value to stack

iii. If the parameter is value-type then box the value type instance using box instruction

iv. By now you will have either a boxed representation for value type parameters or a managed pointer for reference types on the stack. Store the value on stack to object array, created earlier, using stelem instruction

b. Load (push) method signature string to the evaluation stack using ldstr "method signature"

c. Now you have a string instance and an object array at the top of your evaluation stack, in the same order. Use call instruction and call AdvancedTracing::Utility::WriteTraceLineWithData method which will pop the method signature string and object array from the evaluation stack and pass these two to WriteTraceLineWithData method as parameters. This leaves evaluation stack as it was when method execution began.

7. Lastly, save modified assembly back to disk

The entire source code for the application has been attached to the post, you can find it at the top. I am making the core code of TraceInjection class available on this page as well.

Before you start understanding code, I want to remind you that I am a big fan of tracing code in method body to explain logical flow in code as it helps a lot during development/debugging cycle, especially for a large application and a big team of developers. Hence, I strongly recommend everyone to write lot of Trace.WriteLine or similar methods calls from Trace class in code. The code injection approach shown in this post simply automates some bit of it and saves you from doing it manually across entire code base and helps you keep it up to date and consistent as that will almost be a mission impossible. At the same time, I also believe that tracing is one of the best way to comment your code as well. So use this application to your advantage and make best use of CLR tracing framework.

Lastly I would strongly recommend you to read the Advanced Tracing blog post which creates a new database trace listener and explains nuances of CLR tracing and its advantages.

Here’s the entire code for TraceInjection class with the updated logic for logging method parameter values as well:


using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Configuration;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System;
using System.Linq;

namespace CodeInjection
{
    public class TraceInjection
    {
        public bool InjectTracingLine(string assemblyPath, string outputDirectory)
        {
            bool logWithData = false;                           // Boolean value to store developer's preference, whether he wants to inject code which wll dump parameters value as well.
            bool isAssemblyInjected = false;                    // Boolean flagt to indicate whether we have injected code to the assembly under consideration
            bool pointerToValueTypeVariable = false;            // Boolean flag to indicate whether we have a ByRef parameter where the underlying/referenced type is a value type
            MetadataType paramMetaData;                         // Meta data type enum from Mono.Cecil

            TypeSpecification referencedTypeSpec = null;

            CustomAttribute customAttr;
            AssemblyDefinition asmDef;
            TypeReference typeObject;

            Trace.WriteLine(string.Format("InjectTracingLine called for assembly: {0} and outputDirectory: {1}", assemblyPath, outputDirectory));

            string fileName = Path.GetFileName(assemblyPath);
            string newPath = outputDirectory + @"\" + fileName;


            // Check if Output directory already exists, if not, create one
            // ------------------------------------------------------------
            if (!Directory.Exists(outputDirectory))
            {
                Directory.CreateDirectory(outputDirectory);
            }

            try
            {
                // We need reference to AdvancedTracing.Utility type and its 
                // WriteTraceLineWithData method
                // ------------------------------------------------------------
                ModuleDefinition advancedTacingModule = ModuleDefinition.ReadModule(AppDomain.CurrentDomain.BaseDirectory + @"\AdvancedTracing.dll");
                TypeDefinition utilityType = advancedTacingModule.Types.First(t => t.Name == "Utility");
                MethodDefinition loggingMethod = utilityType.Methods.First(m => m.Name == "WriteTraceLine");
                MethodDefinition loggingMethodWithData = utilityType.Methods.First(m => m.Name == "WriteTraceLineWithData");

                // List of new tracing IL instructions which will be added to the method
                List objTracingInstructions = new List();

                // Load assembly
                // ------------------------------------------------------------
                asmDef = AssemblyDefinition.ReadAssembly(assemblyPath);
                
                foreach (ModuleDefinition modDef in asmDef.Modules)
                {
                    // Get System.Object type reference
                    typeObject = modDef.TypeSystem.Object;

                    foreach (TypeDefinition typDef in modDef.Types)
                    {
                        foreach (MethodDefinition metDef in typDef.Methods)
                        {
                            // Check if method has the required custom attribute set
                            // ------------------------------------------------------------
                            if (this.TryGetCustomAttribute(metDef, "AdvancedTracing.LogMethodExecutionEntryAttribute", out customAttr))
                            {
                                // Method has the desired attribute set, edit IL for method
                                Trace.WriteLine("Found method " + metDef.ToString());

                                // Now we gonna inject code so you can flag that assembly has 
                                // been code injected so that updated assembly will be written
                                // back to disk
                                // ------------------------------------------------------------
                                isAssemblyInjected = true;

                                // Check developer's intention whether he wants to just log 
                                // method execution OR method's parameter values as well
                                if (customAttr.HasConstructorArguments)
                                {
                                    // Developer has expilicitly specified his intention
                                    if (customAttr.ConstructorArguments != null
                                        && customAttr.ConstructorArguments.Count > 0)
                                    {
                                        if (!bool.TryParse(customAttr.ConstructorArguments[0].Value.ToString(), out logWithData))
                                        {
                                            // We could not parse the constructor argument to a boolean value
                                            // so we will assume it to be false (which is the default behavior)
                                            logWithData = false;
                                        }
                                    }
                                }
                                else
                                {
                                    // Developer has NOT expilicitly specified his intention
                                    // so we will assume it to be false i.e. don't log data
                                    logWithData = false;
                                }

                                // Get ILProcessor
                                ILProcessor ilProcessor = metDef.Body.GetILProcessor();

                                // Get required counts for the method
                                // ------------------------------------------------------------
                                int intMethodParamsCount = metDef.Parameters.Count;
                                int intArrayVarNumber = metDef.Body.Variables.Count;


                                // Clear the list so that we can reuse the existing list object
                                // ------------------------------------------------------------
                                objTracingInstructions.Clear();

                                
                                // Load method signature string
                                // ------------------------------------------------------------
                                objTracingInstructions.Add(ilProcessor.Create(
                                    OpCodes.Ldstr,
                                    metDef.ToString()
                                ));


                                // If method contains parameters, then emit code to log parameter 
                                // values as well
                                // ------------------------------------------------------------
                                if (intMethodParamsCount > 0 && logWithData)
                                {
                                    // Add metadata for a new variable of type object[] to method body
                                    // .locals init (object[] V_0)
                                    // ------------------------------------------------------------
                                    ArrayType objArrType = new ArrayType(typeObject);
                                    metDef.Body.Variables.Add(new VariableDefinition((TypeReference)objArrType));


                                    // Set InitLocals flag to true. At times, this is set to false
                                    // in case of static mehods and currently Mono.Cecil does not have 
                                    // capability to detect need of this flag and emit it automatically
                                    // ------------------------------------------------------------
                                    metDef.Body.InitLocals = true;

                                    // Create an array of type system.object with 
                                    // same number of elements as count of method parameters
                                    // ------------------------------------------------------------
                                    objTracingInstructions.Add(
                                        ilProcessor.Create(OpCodes.Ldc_I4, intMethodParamsCount)
                                    );

                                    objTracingInstructions.Add(
                                        ilProcessor.Create(OpCodes.Newarr, typeObject)
                                    );

                                    // This instruction will store the address of the newly created 
                                    // array in local variable
                                    // ------------------------------------------------------------
                                    objTracingInstructions.Add(
                                        ilProcessor.Create(OpCodes.Stloc, intArrayVarNumber)
                                    );


                                    // Loop over all the parameters of method and add their value to object[]
                                    // ------------------------------------------------------------
                                    for (int i = 0; i < intMethodParamsCount; i++)
                                    {
                                        paramMetaData = metDef.Parameters[i].ParameterType.MetadataType;
                                        if (paramMetaData == MetadataType.UIntPtr ||
                                            paramMetaData == MetadataType.FunctionPointer ||
                                            paramMetaData == MetadataType.IntPtr ||
                                            paramMetaData == MetadataType.Pointer)
                                        {
                                            // We don't want to log values of these parameters, so skip
                                            // this iteration
                                            break;
                                        }

                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldloc, intArrayVarNumber));
                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldc_I4, i));

                                        // Instance methods have an an implicit argument called "this"
                                        // and hence, we need to refer to actual arguments with +1 position
                                        // whereas, in case of static methods, "this" argument is not there
                                        // ------------------------------------------------------------
                                        if (metDef.IsStatic)
                                        {
                                            objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldarg, i));
                                        }
                                        else
                                        {
                                            objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldarg, i + 1));
                                        }

                                        // Reset boolean flag variable to false
                                        pointerToValueTypeVariable = false;

                                        // If aparameter is passed by reference then you need to use ldind
                                        // ------------------------------------------------------------
                                        TypeReference paramType = metDef.Parameters[i].ParameterType;
                                        if (paramType.IsByReference)
                                        {
                                            referencedTypeSpec = paramType as TypeSpecification;
                                            Trace.WriteLine(string.Format("Parameter Name:{0}, Type:{1}", metDef.Parameters[i].Name, metDef.Parameters[i].ParameterType.Name));

                                            if(referencedTypeSpec != null)
                                            {
                                                switch (referencedTypeSpec.ElementType.MetadataType)
                                                {
                                                    //Indirect load value of type int8 as int32 on the stack
                                                    case MetadataType.Boolean:
                                                    case MetadataType.SByte:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_I1));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    // Indirect load value of type int16 as int32 on the stack
                                                    case MetadataType.Int16:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_I2));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    // Indirect load value of type int32 as int32 on the stack
                                                    case MetadataType.Int32:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_I4));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    // Indirect load value of type int64 as int64 on the stack
                                                    // Indirect load value of type unsigned int64 as int64 on the stack (alias for ldind.i8)
                                                    case MetadataType.Int64:
                                                    case MetadataType.UInt64:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_I8));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    // Indirect load value of type unsigned int8 as int32 on the stack
                                                    case MetadataType.Byte:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_U1));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    // Indirect load value of type unsigned int16 as int32 on the stack
                                                    case MetadataType.UInt16:
                                                    case MetadataType.Char:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_U2));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    // Indirect load value of type unsigned int32 as int32 on the stack
                                                    case MetadataType.UInt32:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_U4));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    // Indirect load value of type float32 as F on the stack
                                                    case MetadataType.Single:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_R4));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    // Indirect load value of type float64 as F on the stack
                                                    case MetadataType.Double:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_R8));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    // Indirect load value of type native int as native int on the stack
                                                    case MetadataType.IntPtr:
                                                    case MetadataType.UIntPtr:
                                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_I));
                                                        pointerToValueTypeVariable = true;
                                                        break;

                                                    default:
                                                        // Need to check if it is a value type instance, in which case
                                                        // we use ldobj instruction to copy the contents of value type
                                                        // instance to stack and then box it
                                                        if (referencedTypeSpec.ElementType.IsValueType)
                                                        {
                                                            objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldobj, referencedTypeSpec.ElementType));
                                                            pointerToValueTypeVariable = true;
                                                        }
                                                        else
                                                        {
                                                            // It is a reference type so just use reference the pointer
                                                            objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldind_Ref));
                                                            pointerToValueTypeVariable = false;
                                                        }
                                                        break;
                                                }
                                            }
                                            else
                                            {
                                                // We dont have complete details about the type of referenced parameter
                                                // So we will just ignore this parameter value
                                            }
                                        }

                                        // If it is a value type then you need to box the instance as we are going 
                                        // to add it to an array which is of type object (reference type)
                                        // ------------------------------------------------------------
                                        if (paramType.IsValueType || pointerToValueTypeVariable)
                                        {
                                            if (pointerToValueTypeVariable)
                                            {
                                                // Box the dereferenced parameter type
                                                objTracingInstructions.Add(ilProcessor.Create(OpCodes.Box, referencedTypeSpec.ElementType));
                                            }
                                            else
                                            {
                                                // Box the parameter type
                                                objTracingInstructions.Add(ilProcessor.Create(OpCodes.Box, paramType));
                                            }
                                        }

                                        // Store parameter in object[] array
                                        // ------------------------------------------------------------
                                        objTracingInstructions.Add(ilProcessor.Create(OpCodes.Stelem_Ref));
                                    }

                                    // Load address of array variable on evaluation stack, to pass
                                    // it as a paremter 
                                    // ------------------------------------------------------------
                                    objTracingInstructions.Add(ilProcessor.Create(OpCodes.Ldloc, intArrayVarNumber));


                                    // Call the method which would write tracing info with data
                                    // ------------------------------------------------------------
                                    objTracingInstructions.Add(ilProcessor.Create(
                                        OpCodes.Call,
                                        metDef.Module.Import(
                                            loggingMethodWithData.GetElementMethod()
                                        )
                                    ));
                                }
                                else
                                {
                                    // Call the method which would write tracing info minus data
                                    // ------------------------------------------------------------
                                    objTracingInstructions.Add(ilProcessor.Create(
                                        OpCodes.Call,
                                        metDef.Module.Import(
                                            loggingMethod.GetElementMethod()
                                        )
                                    ));
                                }


                                // Add the new MSIL to the existing body of method
                                // ------------------------------------------------------------
                                objTracingInstructions.AddRange(metDef.Body.Instructions);
                                metDef.Body.Instructions.Clear();

                                foreach (var IL in objTracingInstructions)
                                {
                                    metDef.Body.Instructions.Add(IL);
                                }
                            }
                        }
                    }
                }

                // Save modified assembly, if code injected
                // ------------------------------------------------------------
                if (isAssemblyInjected)
                {
                    Trace.WriteLine(string.Format("Saving injected assembly at: {0}", newPath));
                    asmDef.Write(newPath, new WriterParameters() { WriteSymbols = true });
                }
                else
                {
                    Trace.TraceInformation(string.Format("No code has been injected to assembly {0}", asmDef.Name.ToString()));
                }
            }
            catch
            {
                // Nothing to be done, just let the caller handle exception 
                // or do logging and so on
                throw;
            }

            return true;
        }

        public bool InjectTracingLine(string assemblyPath)
        {
            Trace.WriteLine("InjectTracingLine called minus outputDirectory, will default to application config file value");
            // New assembly path
            string outputDirectory = ConfigurationManager.AppSettings["OutputDirectory"].ToString();
            return this.InjectTracingLine(assemblyPath, outputDirectory);
        }

        public bool TryGetCustomAttribute(MethodDefinition type, string attributeType, out CustomAttribute result)
        {
            result = null;
            if (!type.HasCustomAttributes)
                return false;

            foreach (CustomAttribute attribute in type.CustomAttributes)
            {
                if (attribute.Constructor.DeclaringType.FullName != attributeType)
                    continue;

                result = attribute;
                return true;
            }

            return false;
        }
    }
}

Happy Coding!!

Vande Mataram!

(A salute to motherland)

P.S. In addition to blogging, I use Twitter to share tips, links, etc. My Twitter handle is: @girishjjain




About the author

Girish Jain works on Microsoft .Net framework technologies and is a big fan of WPF, WCF, and LINQ technologies. He is currently based in India with his wife and a daughter. When not spending time with family, Girish enjoys creating small tools, utilities, frameworks to improve developer productivity.

Sign In