http://www.codinghelmet.com/  

Wear a helmet. Even when coding.

net > sysexpand > reflection > latebinding > manual > intro

Introduction
by Zoran Horvat @zoranh75
September 02, 2011

To use types declared in a .NET assembly, we normally need to add a reference to the assembly to the .NET project, and then we could use the desired types directly. This way of accessing types is called early binding, because all relevant information about the type are gathered at compile time. Term "early" comes from the point of view of the application execution, in regard to which compile time is considered early. Good thing about early binding is that all calls made to assembly's contents can be evaluated once (and that would be at compile time). When application enters execution phase no further effort is needed to access type information. For example, invoking a method does not require finding the method in type's metadata, then verifying the argument types, etc. All these actions have already been done at compile time, and it only remains to pass the arguments to the method and then to invoke it, all using the known calling convention.

All said above is normal operation. But what happens if one does not possess the reference to a .NET assembly at compile time? For example, if we decide to develop add-on feature. Add-on is a piece of software which can be added to the application after the development (and deployment) has been completed. Original program developer proposes the set of rules each add-on would have to fulfill (should any add-on fail comply, software would reject it). The whole add-on feature is typically implemented by specifying the general interfaces and base classes that must be implemented by the type which is considered the add-on. Third party developer would then develop the add-on and provide the assembly, after which application would be able to access it and use it. And here the late binding comes in. Any type defined in such assembly cannot be instantiated directly simply because application did not have the assembly to analyze its metadata at compile time. The assembly was yet to be developed! So reference to it could not be added, and application could not be compiled to use that assembly at time when it was developed.

LateBinding library provides easy to use set of classes that allow binding to an assembly and using public types exposed by that assembly. The goal is to simplify the .NET late binding code as much as possible, primarily by avoiding cumbersome error checking conditions. Having this simplification provided by the LateBinding library, developers may concentrate on the functionality of the main code, rather than typing the same set of complex statements over and over again, each time when application needs to access a feature contained in the late bound assembly.

Example

As a quick demonstration, we will invoke the GetHashCode method on an instance of the BindingTestClass type. This type is defined in the SysExpand.Reflection.LateBinding.Test.exe assembly. We will perform this task in three different ways:

  • Using early assembly binding, i.e. when reference to the assembly is added to the .NET project and type is bound at compile time,
  • Using .NET Framework classes to perform late binding, and
  • Using LateBinding library to perform the late binding.

Early Binding Example

In this section we assume a project with reference to SysExpand.Reflection.LateBinding.Test.exe assembly. Having the assembly reference at hand, we can instantiate the class directly. Anyway, we have to handle any exceptions that might be thrown either from inside the constructor, or from inside the GetHashCode method. This fairly simple piece of code may look something like this:

using System;
using SysExpand.Reflection.LateBinding.Test;

namespace EarlyBinding
{

    class Program
    {

        static void Main(string[] args)
        {

            try
            {
                BindingTestClass inst = new BindingTestClass();
                int result = inst.GetHashCode();
                Console.WriteLine("Method GetHashCode successfully called and returned value {0}.", result);
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("Constructor or GetHashCode method threw an exception:\n{0}", ex);
            }

        }

    }

}

Successful execution of this code would produce output like this:

Method GetHashCode successfully called and returned value 22008501.

Unsuccessful execution may occur if the referenced assembly cannot be found at run time, or if exception is thrown from either the constructor or the invoked method. In case of missing assembly, the try...catch block will not handle the error, because such error actually occurs before entering the Main method.

Late Binding Example Using .NET Framework Classes

Now we can perform the same task as in the previous example, but using late binding features provided by .NET Framework classes. Here is the code:

using System;
using System.Reflection;

namespace LateBindingNET
{

    class Program
    {

        static void Main(string[] args)
        {

            ConstructorInfo ctorInfo = null;
            MethodInfo methodInfo = null;

            try
            {

                Assembly asm = Assembly.Load("SysExpand.Reflection.LateBinding.Test");

                Type type = asm.GetType("SysExpand.Reflection.LateBinding.Test.BindingTestClass");
                if (type == null)
                    throw new System.Exception("Type could not be found in the Assembly.");

                Type[] ctorParamTypes = new Type[] { typeof(Int32), typeof(string) };
                ctorInfo = type.GetConstructor(ctorParamTypes);
                if (ctorInfo == null)
                    throw new System.Exception("Could not find the constructor.");

                methodInfo = type.GetMethod("GetHashCode");
                if (methodInfo == null)
                    throw new System.Exception("Could not find method GetHashCode.");

            }
            catch (System.Exception bindingError)
            {
                Console.WriteLine("Could not invoke method due to a binding error:\n{0}", bindingError);
            }

            if (ctorInfo != null)
            {

                try
                {

                    object[] ctorParams = new object[] { 17, "Some message" };
                    object inst = ctorInfo.Invoke(ctorParams);

                    object result = methodInfo.Invoke(inst, new object[0]);

                    Console.WriteLine("Method GetHashCode successfully called and returned value {0}.", (int)result);

                }
                catch (System.Exception invokingError)
                {
                    Console.WriteLine("Constructor or GetHashCode method threw an exception:\n{0}", invokingError);
                }

            }

        }

    }

}

To execute this piece of code, make sure that SysExpand.Reflection.LateBinding.Test.exe assembly is located in the execution directory, or otherwise late binding will fail. If all set, the output of this program would be the same as with the early binding example.

However, this code is capable of detecting binding errors that occur if assembly cannot be found or if assembly does not expose the requested type. To test this feature, just remove the SysExpand.Reflection.LateBinding.Test.exe assembly from execution directory. In that case, output will change into this:

Could not invoke method due to a binding error:
System.IO.FileNotFoundException: Could not load file or assembly 'SysExpand.Reflection.LateBinding.Test'
or one of its dependencies. The system cannot find the file specified.
File name: 'SysExpand.Reflection.LateBinding.Test'
        at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity,
          Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
        at System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity,
          Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
        at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity,
          StackCrawlMark& stackMark, Boolean forIntrospection)
        at System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity,
          StackCrawlMark& stackMark, Boolean forIntrospection)
        at System.Reflection.Assembly.Load(String assemblyString)
        at LateBindingNET.Program.Main(String[] args) in C:\ConsoleApplication1\ConsoleApplication1\Program.cs:line 29

But additional price is paid for all this comfort. Binding part of the code and actual execution part of the code are separated under two distinct try...catch blocks. Otherwise, it would not be possible to distinguish binding error from exception raised from inside the constructor or from inside the GetHashCode method.

Main disadvantage of this piece of code is its complexity. There is quite a lot of error handling code, and binding is done thru use of types that do not have common naming convention or even common error reporting strategy. For example, assembly loading would raise an exception in case of error, while method binding would simply return null if requested method is not found. In the first case, program would output exception message as an error, while in the second case error report would have to be produced by the caller.

Next example demonstrates use of the LateBinding class library on the same problem.

Late Binding Example Using SysExpand

To use the SysExpand LateBinding library, we need to add reference to the SysExpand.Reflection.LateBinding.dll assembly. Once done, the following code would bind to the SysExpand.Reflection.LateBinding.Test.exe, instantiate the BindingTestClass type and invoke its GetHashCode method. Make sure that SysExpand.Reflection.LateBinding.Test.exe assembly is located in the execution directory before running the code.

using System;
using SysExpand.Reflection.LateBinding;

namespace LateBinding
{

    class Program
    {

        static void Main(string[] args)
        {

            try
            {

                AssemblyRef asm = AssemblyRef.BindAssembly("SysExpand.Reflection.LateBinding.Test");

                TypeRef type = asm.BindType("SysExpand.Reflection.LateBinding.Test.BindingTestClass");

                Type[] ctorParamTypes = new Type[] { typeof(Int32), typeof(string) };
                object[] ctorParams = new object[] { 17, "Some message" };

                InstanceRef inst = type.BindInstance(ctorParamTypes, ctorParams);

                MethodRef method = inst.BindMethod("GetHashCode");

                object result = null;
                bool success = method.Invoke(new object[0], out result);

                if (success)
                    Console.WriteLine("Method GetHashCode successfully called and returned value {0}.", (int)result);
                else
                    Console.WriteLine("Method GetHashCode could not be invoked due to an error:\n{0}", method.InvokingErrorMessage);

            }
            catch (System.Exception ex)
            {
                Console.WriteLine("Constructor or GetHashCode method threw an exception:\n{0}", ex);
            }

        }

    }

}

If all went well, this code would report a message like this:

Method GetHashCode successfully called and returned value 62476613.

Now if SysExpand.Reflection.LateBinding.Test.exe assembly is removed from the execution directory, binding will fail and code will output an error like this:

Method GetHashCode could not be invoked due to an error:
Instance not bound. Parent type not bound. Assembly not bound. Error loading assembly "SysExpand.Reflection.LateBinding.Test":
Could not load file or assembly 'SysExpand.Reflection.LateBinding.Test' or one of its dependencies. The system cannot find the file specified.

The order of steps made to invoke the method using SysExpand library was quite straight forward:

  • Bind to a .NET assembly, which effectively means that assembly would be loaded.
  • Bind to a .NET type, which effectively means that type will be found in the assembly and reflected upon.
  • Bind to an instance, which effectively means that instance of the bound type would be created by invoking the specifically requested constructor.
  • Bind to a method, which effectively means that type will be reflected to find the instance-level method with specified signature.
  • Invoke the method, which would effectively invoke the method which was reflected in the previous step, passing it the specific list of parameter values.

Each of these steps can be implemented in a single statement when using SysExpand LateBinding class library. Error testing boils down to testing the value of IsBound property at any intermediate step. To save the typing, this was done only at the very end of the code (note that error messages are accumulated in the final error message, thus justifying the decision to postpone the error testing until all binding steps are done).

Comparison of Methods

Both presented .NET late binding examples perform exactly the same task. The first example has used .NET Framework classes and the second example has used SysExpand LateBinding class library. As can be verified from the code given above, the first case, using .NET Framework classes directly, incurs some overheads:

  • The code using .NET Framework classes is almost twice as long as the code using SysExpand LateBinding library.
  • The code using .NET framework classes is much more cryptic. This is due to using a set of very different classes and methods that do not have common naming conventions and return types.
  • .NET framework classes report errors in different ways. Assembly.Load static method throws exception on error while some other calls simply return null if no valid result could be produced (e.g. GetMethod of the System.Type class). This leads to handling binding errors differently, further complicating the code, because developer has to generate own error reports.
  • When using .NET framework, instantiating the type is done in two steps. First a constructor is found, and then it is invoked to produce an instance object. The problem may arise if constructor is invoked with invalid arguments, in which case exception is raised. However, exception may be raised from inside the constructor, which would be the "legal" exception (in sense that constructor was correctly found and invoked). If one try...catch statement is present, the two errors could not be distinguished. For this reason binding to a constructor must be physically divided from constructor invocation in separate try...catch block. This obstacle does not exist when using LateBinding library, because constructor binding error is detected by simply testing the IsBound property of the InstanceRef class instance.

See also:

Published: Sep 2, 2011; Modified: Apr 14, 2013

ZORAN HORVAT

Zoran is software architect dedicated to clean design and CTO in a growing software company. Since 2014 Zoran is an author at Pluralsight where he is preparing a series of courses on object-oriented and functional design, design patterns, writing unit and integration tests and applying methods to improve code design and long-term maintainability.

Follow him on Twitter @zoranh75 to receive updates and links to new articles.

Watch Zoran's video courses at pluralsight.com (requires registration):

Making Your C# Code More Object-Oriented

This course will help leverage your conceptual understanding to produce proper object-oriented code, where objects will completely replace procedural code for the sake of flexibility and maintainability. More...

Advanced Defensive Programming Techniques

This course will lead you step by step through the process of developing defensive design practices, which can substitute common defensive coding, for the better of software design and implementation. More...

Tactical Design Patterns in .NET: Creating Objects

This course sheds light on issues that arise when implementing creational design patterns and then provides practical solutions that will make our code easier to write and more stable when running. More...

Tactical Design Patterns in .NET: Managing Responsibilities

Applying a design pattern to a real-world problem is not as straight-forward as literature implicitly tells us. It is a more engaged process. This course gives an insight to tactical decisions we need to make when applying design patterns that have to do with separating and implementing class responsibilities. More...

Tactical Design Patterns in .NET: Control Flow

Improve your skills in writing simpler and safer code by applying coding practices and design patterns that are affecting control flow. More...

Writing Highly Maintainable Unit Tests

This course will teach you how to develop maintainable and sustainable tests as your production code grows and develops. More...

Improving Testability Through Design

This course tackles the issues of designing a complex application so that it can be covered with high quality tests. More...

Share this article

webmasters