Free C#, .Net and Sql server video tutorial for beginners and intermediate programmers.
How .NET finds the assemblies during program execution - Part 5
1. .NET figures out what version is needed : Usually the information about the dependant assemblies is present in the application's assembly manifest. CLR checks the application configuration file, publisher policy file(if exists), and machine config file for information that overrides the version information stored in the calling assembly's manifest.
2. .NET searches GAC (Global Assembly Cache) : .NET searches GAC only if the assembly is strongly named.
3. If the assembly is not found in the GAC, and if there is a .config file, then .NET searches the location in the cofiguration file, else .NET searches directory containing the executable (.EXE)
4. If the assembly is not found, the application terminates with error.
Note: Version checking is not done for Weakly Named Assemblies (Assemblies without a strong name)
What is GAC. How and when to install an assembly into GAC - Part 4
GAC stands for Global Assembly Cache and contains strong named assemblies. Assemblies in the GAC can be shared by all applications running on that machine, without having to copy the assembly locally. It is recommended to install an assembly into GAC, only when required and shared by applications, otherwise they should be kept private. You shouldn't add an assembly into the GAC, if you wish to deploy your application to another machine using XCopy deployment. This is because in XCopy deployment, we only copy the application files to the target machine and not the GAC contents. XCopy deployment is simply copying files from one location to another.
With the introduction of .NET 4.0, we have 2 GAC's. One for DotNet 2.0 to 3.5 assemblies and the other for .NET 4.0 assemblies. The following are the paths for the 2 GAC's
1. C:\Windows\Assembly - For .NET 2.0 - 3.5 assemblies
2. C:\WINDOWS\Microsoft.NET\assembly - For .NET 4.0 assemblies
To install an assembly into the GAC, the assembly must be strongly named, otherwise you get an error stating - Failure adding assembly to the cache: Attempt to install an assembly without a strong name. There are 2 ways to install an assembly into GAC.
1. Simply Drag and Drop
2. Use GacUtil.exe (GAC Utility Tool)
To install an assembly using gacutil, use the following command. This command installs SampleAssembly.dll into the GAC. If you have build this project using .NET framwork 4.0 then look in C:\WINDOWS\Microsoft.NET\assembly, else look in C:\Windows\Assembly.
Gacutil -i C:\SampleProject\SampleAssembly.dll
Note: If you are using Visual Studio 2010, then by default the target framework for any new project is .NET 4.0. If you want to change the target framework, right click the project and select properties. In the properties window, you can change the target framework version.
To uninstall an assembly from the GAC, using GAC utility, use the following command.
Gacutil -u MyClassLibrary
If there are multiple versions of MyClassLibrary assembly, in the GAC, then all these versions will be removed by the above command. If you want to remove only one of the assemblies then specify the full name as shown below.
gacutil -u ClassLibrary,Version=1.0.0.0,PublicKeyToken=eeaabf36d7783129
Note: Please make sure there are no spaces between Comma(,) and the words "Version" and PublicKeyToken, otherwise you get an error stating Unknown option: Version=1.0.0.0. Also, don't specify the assembly extension (.dll or .exe) when uninstalling, otherwise the assembly will not be uninstalled. You will just get a message stating Number of assemblies uninstalled = 0
Strong naming an assembly - Part 3
Strong naming an assembly or Signing an assembly with strong name.
In .NET assemblies can be broadly classified into 2 types
1. Weak Named Assemblies
2. Strong Named Assemblies
An assembly name consists of 4 Parts
1. Simple textual name.
2. Version number.
3. Culture information (If provided, otherwise the assembly is language neutral)
4. Public key token
In .NET assemblies can be broadly classified into 2 types
1. Weak Named Assemblies
2. Strong Named Assemblies
An assembly name consists of 4 Parts
1. Simple textual name.
2. Version number.
3. Culture information (If provided, otherwise the assembly is language neutral)
4. Public key token
We use AssemblyVersion attribute to specify the Assembly version. The default is 1.0.0.0. The version number of an assembly consists of the following four parts:
1. Major Version
2. Minor Version
3. Build Number
4. Revision Number
You can specify all the values or you can default the Revision and Build Numbers by using the '*' as shown below:
[assembly: AssemblyVersion("2.1.*")]
AssemblyCulture attribute is used for specifying the culture. By default an assembly is language neutral, as the AssemblyCulture attribute contains empty string. If you specify any string other than an empty string for the culture parameter, the assembly becomes a satellite assembly. In fact, compilers use this attribute to distinguish between main assembly (language neutral) and a satellite assembly. We will talk about satellite assemblies in a later session.
We use AssemblyKeyFile attribute to sign the assembly with a strong name. To the constructor of AssemblyKeyFile attribute, we need to pass the path of the key file, that contains the private and public key. To generate the key file
1. Open Visual Studio Command Prompt
2. Type the command and press enter: sn.exe -k c:\KeyFile.snk
The key file with name KeyFile.snk should be generated in the C: drive. In SN.exe, SN stands for Strong Name. Key files have the extension of .snk
Finally, In AssemblyInfo.cs file of the project, specify AssemblyKeyFile attribute as shown below and build the project. This process will strongly name an assembly.
[assembly: AssemblyKeyFile("KeyFile.snk")]
A strongly named assembly should have all of the following
1. The textual assembly name.
2. The assembly Version number.
3. The assembly should have been signed with private/public key pair.
If the assembly is not signed with private/public key pair, the assembly is weak named and not guaranteed to be unique, and may cause DLL hell. Strong named assemblies are guaranteed to be unique and solves DLL hell. You cannot install an assembly into GAC unless, the assembly is strongly named.
In the upcoming video sessions we will discuss
1. What is GAC. How and when to install an assembly into GAC?
2. What is DLL HELL?
3. How is DLL HELL solved with .NET?
ILDASM and ILASM - Part 2
From Part 1 of DotNet basics videos, we understood that, compiling any .NET application would produce an assembly. Assemblies have an extension of .DLL or .EXE. For example if you compile a windows or Console application, you get a .EXE, where as when we compile a web or Class library project we get a .DLL. Please watch Part 1, if you haven't done so already.
The entire source code of a project is compiled into Intermediate Language and packaged into the assembly. A .NET assembly consists of Manifest and Intermediate language. Manifest contains metadata about the assembly like the name, version, culture and strong name information. Metadata also contains information about the referenced assemblies. Each reference includes the dependent assembly's name, assembly metadata (version, culture, operating system, and so on), and public key, if the assembly is strong named.
Some information in the assembly manifest can be modified using attributes. For example to modify the version number follow these steps
1. Expand the properties folder in solution explorer. Every project in .NET has a properties folder.
2. Open AssemblyInfo.cs file that is present under properties folder.
3. In this file, you should see AssemblyVersion attribute, which is defaulted to 1.0.0.0. Change this to 2.0.0.0 and rebuild the solution.
4. Now open the assembly using ILDASM.exe
To peek inside an assembly with ILDASM follow these steps.
1. Navigate to Visual Studio Command Prompt (Start -> All Programs -> Microsoft Visual Studio 2010 -> Visual Studio Tools -> Right Click on Visual Studio Command Prompt 2012 and select "Run as Administrator")
2. Once you have the "Visual Studio Command Prompt 2012" open, type in the following command and press enter
Ildasm.exe C:\YourDirectoryPath\YourAssembly.exe
This command should open the assembly and you will find the manifest and the types (classes, structs etc..) in the assembly. At the bottom you can see the version of the assembly.
If you want to save the Intermediate Language to a text file.
1. Select File Menu from the ILDASM tool.
2. Select Dump and you will see "Dump Options Window"
3. Click OK on "Dump Options Window"
4. Now enter the file name of your choice. For this example let's enter sample and save it to the C: drive.
5. Now navigate to C: drive in windows explorer and you should see Sample.il
6. Open Sample.il with notepad and you should see assembly metadata and IL(Intermediate Language).
If you want to rebuild an Assembly from the Sample.il file we can use a tool ILASM.exe
1. Type the following command in "Visual Studio Command Prompt" and press enter
ILASM.exe C:\Sample.il
2. Now navigate to C: drive in windows explorer and you should see Sample.exe
We use ILDASM (Intermediate Language Disassembler) to peek at the assembly manifest and IL. You can also use this tool to export manifest and IL to a text file.
We use ILASM.exe (Intermediate Language Assembler) to reconstruct an assembly from a text file that contains manifest and IL.
.NET Program Execution - Part 1
Let us first understand how VB6 or C++ programs (Non Dotnet applications) used to execute.
We know that computers only understand machine level code. Machine level code is also called as native or binary code. So, when we execute a VB6 or C++ program, the respective language compiler, compiles the respective language source code into native code, which can then be understood by the underlying operating system and hardware. This process is depicted in the image below.
Native code is specific (native) to the operating system on which it is generated. If you take this compiled native code and try to run on another operating system it will fail. So the problem with this style of program execution is that, it is not portable from one platform to another platform.
Let us now understand, how a .Net program executes. Using dotnet we can create different types of applications. A few of the common types of .NET applications include Web, Windows, Console and Mobile Applications. Irrespective of the type of the application, when you execute any .NET application the following happens
1. The .NET application gets compiled into Intermediate language (IL). IL is also referred as Common Intermediate language (CIL) and Microsoft Intermediate language (MSIL). Both .NET and non .NET applications generate an assembly. Assemblies have an extension of .DLL or .EXE. For example if you compile a windows or Console application, you get a .EXE, where as when we compile a web or Class library project we get a .DLL. The difference between a .NET and NON .NET assembly is that, DOTNET Assembly is in intermediate language format where as NON DOTNET assembly is in native code format.
2. NON DOTNET applications can run directly on top of the operating system, where as DOTNET applications run on top of a virtual environment called as Common Language Runtime (CLR). CLR contains a component called Just In-Time Compiler (JIT), which will convert the Intermediate language into native code which the underlying operating system can understand.
So, in .NET the application execution consists of 2 steps
1. Language compiler, compiles the Source Code into Intermediate Language (IL)
2. JIT compiler in CLR converts, the IL into native code which can then be run on the underlying operating system.
This process is shown in the image below.
Since, a .NET assembly is in Intermedaite Language format and not native code, .NET assemblies are portable to any platform, as long as the target platform has the Common Language Runtime (CLR). The target platform's CLR converts the Intermedaite Language into native code that the underlying operating system can understand. Intermediate Languge is also called as managed code. This is because CLR manages the code that runs inside it. For example, in a VB6 program, the developer is responsible for de-allocating the memory consumed by an object. If a programmer forgets to de-allocate memory, we may run into hard to detecct out of memory exceptions. On the other hand a .NET programmer need not worry about de-allocating the memory consumed by an object. Automatic memory management, also known as grabage collection is provided by CLR. Apart, from garbage collection, there are several other benefits provided by the CLR, which we will discuss in a later session. Since, CLR is managing and executing the Intermediate Language, it (IL) is also called as managed code.
.NET supports different programming languages like C#, VB, J#, and C++. C#, VB, and J# can only generate managed code (IL), where as C++ can generate both managed code (IL) and un-managed code (Native code).
The native code is not stored permanently anywhere, after we close the program the native code is thrown awaya. When we execute the program again, the native code gets generated again.
.NET program is similar to java program execution. In java we have byte codes and JVM (Java Virtual Machine), where as in .NET we Intermediate Language and CLR (Common Language Runtime)
We know that computers only understand machine level code. Machine level code is also called as native or binary code. So, when we execute a VB6 or C++ program, the respective language compiler, compiles the respective language source code into native code, which can then be understood by the underlying operating system and hardware. This process is depicted in the image below.
Native code is specific (native) to the operating system on which it is generated. If you take this compiled native code and try to run on another operating system it will fail. So the problem with this style of program execution is that, it is not portable from one platform to another platform.
Let us now understand, how a .Net program executes. Using dotnet we can create different types of applications. A few of the common types of .NET applications include Web, Windows, Console and Mobile Applications. Irrespective of the type of the application, when you execute any .NET application the following happens
1. The .NET application gets compiled into Intermediate language (IL). IL is also referred as Common Intermediate language (CIL) and Microsoft Intermediate language (MSIL). Both .NET and non .NET applications generate an assembly. Assemblies have an extension of .DLL or .EXE. For example if you compile a windows or Console application, you get a .EXE, where as when we compile a web or Class library project we get a .DLL. The difference between a .NET and NON .NET assembly is that, DOTNET Assembly is in intermediate language format where as NON DOTNET assembly is in native code format.
2. NON DOTNET applications can run directly on top of the operating system, where as DOTNET applications run on top of a virtual environment called as Common Language Runtime (CLR). CLR contains a component called Just In-Time Compiler (JIT), which will convert the Intermediate language into native code which the underlying operating system can understand.
So, in .NET the application execution consists of 2 steps
1. Language compiler, compiles the Source Code into Intermediate Language (IL)
2. JIT compiler in CLR converts, the IL into native code which can then be run on the underlying operating system.
This process is shown in the image below.
Since, a .NET assembly is in Intermedaite Language format and not native code, .NET assemblies are portable to any platform, as long as the target platform has the Common Language Runtime (CLR). The target platform's CLR converts the Intermedaite Language into native code that the underlying operating system can understand. Intermediate Languge is also called as managed code. This is because CLR manages the code that runs inside it. For example, in a VB6 program, the developer is responsible for de-allocating the memory consumed by an object. If a programmer forgets to de-allocate memory, we may run into hard to detecct out of memory exceptions. On the other hand a .NET programmer need not worry about de-allocating the memory consumed by an object. Automatic memory management, also known as grabage collection is provided by CLR. Apart, from garbage collection, there are several other benefits provided by the CLR, which we will discuss in a later session. Since, CLR is managing and executing the Intermediate Language, it (IL) is also called as managed code.
.NET supports different programming languages like C#, VB, J#, and C++. C#, VB, and J# can only generate managed code (IL), where as C++ can generate both managed code (IL) and un-managed code (Native code).
The native code is not stored permanently anywhere, after we close the program the native code is thrown awaya. When we execute the program again, the native code gets generated again.
.NET program is similar to java program execution. In java we have byte codes and JVM (Java Virtual Machine), where as in .NET we Intermediate Language and CLR (Common Language Runtime)
Part 60 - C# Tutorial - difference between System.String and System.Text.StringBuilder
Strings of type StringBuilder are mutable where as strings of type System.String are immutable. As StringBuilder objects are mutable, they offer better performance than string objects of type System.String, when heavy string manipulation is involved.
Part 60 - C# Tutorial - difference between System.String and System.Text.StringBuilder
Let's understand the meaning of mutable and immutable strings with an example.
using System;
public class MainClass
{
public static void Main()
{
string userString = "C#";
userString += " Video";
userString += " Tutorial";
userString += " for";
userString += " beginners";
Console.WriteLine(userString);
}
}
In this example, userString variable is changed 5 times.
1. C#
2. C# => C# Video
3. C# Video => C# Video Tutorial
4. C# Video Tutorial => C# Video Tutorial for
5. C# Video Tutorial for => C# Video Tutorial for beginners
Since, userString variable is of type System.String, and when we change this string 5 times, we end up with 5 string objects on the heap as shown in the diagram below. Immutable means, once a string object is created it cannot be changed, without creating another new string object. So in our example, When we initialize userString variable to "C#" we get one immutable string object on the heap. When we concatenate " Video" word to userString variable, the first created "C#" string object is orphaned(userString variable no longer points to this object). Now another new string object with words "C# Video" will be created to which the userString variable points to. So this process continues until, userString reference variable, points to the last string object (C# Video Tutorial for beginners), leaving the other 4 string onbjects on the heap(orphaned), until they are garbage collected, increasing the pressure on memory.
But on the other hand, StringBuilder string objects are mutable, meaning they can be changed inplace, without the need of creating another new StringBuilder object. The above example is rewritten using StringBuilder object.
using System;
using System.Text;
namespace Pragim
{
public class MainClass
{
public static void Main()
{
StringBuilder userStringBuilder =
new StringBuilder("C#");
userStringBuilder.Append(" Video");
userStringBuilder.Append(" Tutorial");
userStringBuilder.Append(" for");
userStringBuilder.Append(" beginners");
Console.WriteLine(userStringBuilder.ToString());
}
}
}
With StringBuilder, no matter how many times you manipulate a string, you will ever have only one instance.

So in brief, here are the differences between String and StringBuilderobjects.
1. Objects of type StringBuilder are mutable where as objects of type System.String are immutable.
2. As StringBuilder objects are mutable, they offer better performance than string objects of type System.String.
3. StringBuilder class is present in System.Text namespace where String class is present in System namespace.
Just imagine, the number of orphaned string objects that get created on the heap when you have a program as shown below.
using System;
namespace Pragim
{
public class MainClass
{
public static void Main()
{
string strNumbers = string.Empty;
for (int i = 0; i < 1000; i++)
{
strNumbers += i.ToString() + " ";
}
Console.WriteLine(strNumbers);
}
}
}
Part 59 - C# Tutorial - Difference between Convert.ToString() and ToString()
To understand the difference consider the example below. The ToString() method, expects the instance on which you are invoking to be NOT NULL. If the object is NULL, you get a NULL Reference exception.
using System;
public class MainClass
{
public static void Main()
{
Customer C1 = null;
Console.WriteLine(C1.ToString());
}
}
public class Customer
{
public string Name { get; set; }
}
using System;
public class MainClass
{
public static void Main()
{
Customer C1 = null;
Console.WriteLine(C1.ToString());
}
}
public class Customer
{
public string Name { get; set; }
}
Part 59 - C# Tutorial - Difference between Convert.ToString() and ToString()
On the other hand, Convert.ToString() returns an empty string if the object is NULL.
using System;
public class MainClass
{
public static void Main()
{
Customer C1 = null;
Console.WriteLine(Convert.ToString(C1));
}
}
public class Customer
{
public string Name { get; set; }
}
So in summary, Convert.ToString() handles null, while ToString() doesn't, and throws a NULL Reference exception. Depending on the type of the application, architecture and what you are trying to achieve, you choose one over the other.
Part 58 - C# Tutorial - Why should you override Equals() method
In this session, let's understand the difference between "==" operator and Equals() method. In C#, every type directly or indirectly inherits from System.Object. So, the Equals() virtual method, that has a default implementation is available in every type via inheritance. In this example, variables i and j are integers. So, == and Equals() method returns true, since i and j, both variables have a value of 10.
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
int i = 10;
int j = 10;
Console.WriteLine(i == j);
Console.WriteLine(i.Equals(j));
}
}
}
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
int i = 10;
int j = 10;
Console.WriteLine(i == j);
Console.WriteLine(i.Equals(j));
}
}
}
Part 58 - C# Tutorial - Why should you override Equals() method
Along the same lines, the sample program below, compares 2 enums and both, the == operator and Equals() method returns true, since bothe direction1 and direction2 enums has the same underlying integer value of 1.
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
Direction direction1 = Direction.East;
Direction direction2 = Direction.East;
Console.WriteLine(direction1 == direction2);
Console.WriteLine(direction1.Equals(direction2));
}
}
public enum Direction
{
East = 1,
West = 2,
North = 3,
South = 4
}
}
However, if the type is a reference type, then by default "==" operator checks for reference equality and .Equals() method checks for value equality. Let's understand what we mean by reference and value equality.
In the example below, C1 and C2 are 2 different object reference variables, but they point to the same object. Keep in mind, object reference variables are different from objects. Object reference variables, stay on the stack and are pointers to actual objects on the heap. Since, C1 and C2 both refer to the same object, the reference equality and value equality is true. Value equality means that two objects contain the same values. In this example, the actual object is only one, so obviously the values are also equal. If two objects have reference equality, then they also have value equality, but value equality does not guarantee reference equality.
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
Customer C1 = new Customer();
C1.FirstName = "Simon";
C1.LastName = "Tan";
Customer C2 = C1;
Console.WriteLine(C1 == C2);
Console.WriteLine(C1.Equals(C2));
}
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
For the example below, == operator returns False. This makes sense because C1 and C2 are referring to different objects. However, .Equals() method returns flase, inspite of the values across C1 and C2 being the same. Hence, it makes sense to override, the Equals() method to return true when the values across the objects are same.
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
Customer C1 = new Customer();
C1.FirstName = "Simon";
C1.LastName = "Tan";
Customer C2 = new Customer();
C2.FirstName = "Simon";
C2.LastName = "Tan";
Console.WriteLine(C1 == C2);
Console.WriteLine(C1.Equals(C2));
}
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
The example below overrides, Equals() method. When overriding Equals() method, make sure the passed in object is not null and can be casted to the type we are comparing. When overriding Equals(), you also need to override GetHashCode(), otherwise you get a compiler warning.
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
Customer C1 = new Customer();
C1.FirstName = "Simon";
C1.LastName = "Tan";
Customer C2 = new Customer();
C2.FirstName = "Simon";
C2.LastName = "Tan";
Console.WriteLine(C1 == C2);
Console.WriteLine(C1.Equals(C2));
}
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
// If the passed in object is null
if (obj == null)
{
return false;
}
if (!(obj is Customer))
{
return false;
}
return (this.FirstName == ((Customer)obj).FirstName)
&& (this.LastName == ((Customer)obj).LastName);
}
public override int GetHashCode()
{
return FirstName.GetHashCode() ^ LastName.GetHashCode();
}
}
}
Part 57 - C# Tutorial - Why should you override ToString() method
Click here for the text article related to this video.
Part 57 - C# Tutorial - Why should you override ToString() method
Part 56 - C# Tutorial - Generics in C#
Generics are introduced in C# 2.0. Generics allow us to design classes and methods decoupled from the data types. Generic classes are extensively used by collection classes available in System.Collections.Generic namespace. Click here to watch the video on generic collection classes.
In this example, AreEqual(int value1, int value2) only works with int data type. If, we pass any other data type, we get a compiler error. So, AreEqual() method in Calculator class is tightly coupled with the int data type, and prevents it from being used with any other data type.
In this example, AreEqual(int value1, int value2) only works with int data type. If, we pass any other data type, we get a compiler error. So, AreEqual() method in Calculator class is tightly coupled with the int data type, and prevents it from being used with any other data type.
Part 56 - C# Tutorial - Generics in C#
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
bool Equal = Calculator.AreEqual(1, 2);
if (Equal)
{
Console.WriteLine("Equal");
}
else
{
Console.WriteLine("Not Equal");
}
}
}
public class Calculator
{
public static bool AreEqual(int value1, int value2)
{
return value1 == value2;
}
}
}
It's a compile time error to invoke AreEqual() method with string parameters.
bool Equal = Calculator.AreEqual("A", "B");
One way of making AreEqual() method reusable, is to use object type parameters. Since, every type in .NET directly or indirectly inherit from System.Object type, AreEqual() method works with any data type, but the problem is performance degradation due to boxing and unboxing happening.
Also, AreEuqal() method is no longer type safe. It is now possible to pass integer for the first parameter, and a string for the second parameter. It doesn't really make sense to compare strings with integers.
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
bool Equal = Calculator.AreEqual("A", "B");
if (Equal)
{
Console.WriteLine("Equal");
}
else
{
Console.WriteLine("Not Equal");
}
}
}
public class Calculator
{
public static bool AreEqual(object value1, object value2)
{
return value1 == value2;
}
}
}
So, the probem with using System.Object type is that
1. AreEqual() method is not type safe
2. Performance degradation due to boxing and unboxing.
Both of these issues can be solved with generics and still make AreEqual() method work with different data types. The re written example using generics is shown below.
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
bool Equal = Calculator.AreEqual<int>(2, 1);
if (Equal)
{
Console.WriteLine("Equal");
}
else
{
Console.WriteLine("Not Equal");
}
}
}
public class Calculator
{
public static bool AreEqual<T>(T value1, T value2)
{
return value1.Equals(value2);
}
}
}
To make AreEqual() method generic, we specify a type parameter using angular brackets as shown below.
public static bool AreEqual<T>(T value1, T value2)
At the point, When the client code wants to invoke this method, they need to specify the type, they want the method to operate on. If the user wants the AreEqual() method to work with integers, they can invoke the method specifying int as the datatype using angular brackets as shown below.
bool Equal = Calculator.AreEqual<int>(2, 1);
To operate with string data type
bool Equal = Calculator.AreEqual<string>("A", "B");
In this example, we made the method generic. Along the same lines, it is also possible to make classes, interfaces and delegates generic.
Part 55 - C# Tutorial - Late binding using reflection
In this session we will understand
1. Early binding and late binding
2. The difference between the two approaches
Early Binding Example:
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
Customer C1 = new Customer();
string fullName = C1.GetFullName("Pragim", "Tech");
Console.WriteLine("Full Name = {0}", fullName);
}
}
public class Customer
{
public string GetFullName(string FirstName, string LastName)
{
return FirstName + " " + LastName;
}
}
}
1. Early binding and late binding
2. The difference between the two approaches
Early Binding Example:
using System;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
Customer C1 = new Customer();
string fullName = C1.GetFullName("Pragim", "Tech");
Console.WriteLine("Full Name = {0}", fullName);
}
}
public class Customer
{
public string GetFullName(string FirstName, string LastName)
{
return FirstName + " " + LastName;
}
}
}
Part 55 - C# Tutorial - Late binding using reflection
In this example, we have the knowledge of Customer class at compile time. So, we are able to create the instance of the Customer class using the new operator. We are also able to invoke the GetFullName() method using C1. Intellisense detects the presence of this method and the number and type of parameters that need to be passed in. If you make any mistake in the name of the method, or the number and type of parameters, those mistakes will be immediately raised as compiler errors.
Late Binding Example:
using System;
using System.Reflection;
namespace Pragim
{
public class MainClass
{
private static void Main()
{
// Load the current executing assembly as the Customer class is present in it.
Assembly executingAssembly = Assembly.GetExecutingAssembly();
// Load the Customer class for which we want to create an instance dynamically
Type customerType = executingAssembly.GetType("Pragim.Customer");
// Create the instance of the customer type using Activator class
object customerInstance = Activator.CreateInstance(customerType);
// Get the method information using the customerType and GetMethod()
MethodInfo getFullName = customerType.GetMethod("GetFullNames");
// Create the parameter array and populate first and last names
string[] methodParameters = new string[2];
methodParameters[0] = "Pragim"; //FirstName
methodParameters[1] = "Tech"; //LastName
// Invoke the method passing in customerInstance and parameters array
string fullName = (string)getFullName.Invoke(customerInstance, methodParameters);
Console.WriteLine("Full Name = {0}", fullName);
}
}
public class Customer
{
public string GetFullName(string FirstName, string LastName)
{
return FirstName + " " + LastName;
}
}
}
Let's assume we don't have the knowledge of Customer class at compile time, and it will be provided only at run time. In this case we need to bind to the Customer class at runtime.
1. Load the assembly which contains the Customer class. In our case, the Customer class is present in the same assembly as the MainClass. So, we use Assembly.GetExecutingAssembly() to load the current executing assembly. On the Assembly class, there are several static methods which can be used to load an assembly at runtime dynamically.
2. Next, we load the Customer class for which we want to create an instance dynamically using executingAssembly.GetType("Pragim.Customer"). Make sure you pass in the fully qualified name to the GetType() method, including the namespace. Otherwise you risk getting a NullReferenceException at runtime.
3. Create the instance of the Customer class using Activator.CreateInstance(customerType).
4. Once we have the Customer instance, now get the method information which we want to invoke dynamically. we use customerType.GetMethod("GetFullName").
5. The GetFullName() method expects 2 string parameters. So, we need to create a string array, and populate it with the first and last name parameters.
6. Finally, invoke the method passing in customerInstance and parameters array.
If you mis-spell the method name or if you pass in the wrong number or type of parameters, you wouldn't get a compiler error, but the application crashes at runtime.
Difference between early and late binding:
1. Early binding can flag errors at compile time. With late binding there is a risk of run time exceptions.
2. Early binding is much better for performance and should always be preferred over late binding. Use late binding only when working with onjects that are not available at compile time.
Subscribe to:
Comments (Atom)