Types in Assemblies


The previous example was highly dynamic; however, the datatype X was still declared as usual and the type information structure was obtained via the typeof operator. It is possible for X to be declared in another assembly and then loaded into a program. That way, the entire picture is dynamic.

To obtain information about an assembly, an Assembly object is first created. The class Assembly does not define a public constructor. Instead, an object is created by calling one of the methods of Assembly. The one to be used in the following example is LoadFrom(); which loads an assembly given its filename. The form to be used is as follows.

static Assembly LoadFrom(string filename);

When an assembly object is available, the types that it contains can be discovered via the method GetTypes(). The form of this method is shown below.

Type[] GetTypes()

The method returns an array of types which are contained in the assembly.

The next example comes in the form of two projects. The first project creates an assembly called Runtime8.dll. This assembly merely contains the class X. The source code for the project Runtime8 is shown below.

// Runtime8 - An Assembly holding a single Class.

using System;

class X
{
    public int x;
    public int y;

    public X(int i) { x = i; y = i; }

    public X(int i, int j) { x = i; y = j; }

    public int Sum() { return x + y; }

    public void Set(int i, int j)
    {
        x = i; y = j;
        Console.WriteLine("In Set(int,int), value={0}", this);
    }

    public void Set(double u, double v)
    {
        x = (int)u; y = (int)v;
        Console.WriteLine("In Set(double,double), value={0}", this);
    }

    public override string ToString()
    {
        return "(" + x + "," + y + ")";
    }
}

This is the same class as previously encountered. The logic for accessing the class has been removed and placed in the next project. The option in Visual Studio for creating a class library has been used and this creates a .Net Assembly with a .dll suffix instead of an executable (which is a special type of assembly). Once this assembly is compiled, the next project is possible - Runtime9. The source code for the program that accesses the assembly Runtime8 is shown below.

// Runtime9 - Loading Types from Assemblies

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        Assembly A = Assembly.LoadFrom("..\\..\\..\\Runtime8\\bin\\debug\\Runtime8.dll");

        Type[] types = A.GetTypes();

        Type t = types[0];

        ConstructorInfo[] constructors = t.GetConstructors();

        Console.WriteLine("Constructors are as follows");

        foreach (ConstructorInfo ci in constructors)
        {
            Console.Write(t.Name + "(");

            ParameterInfo[] parameters = ci.GetParameters();

            for (int i = 0; i < parameters.Length; i++)
            {
                Console.Write(parameters[i].ParameterType.Name + " " +
                              parameters[i].Name);

                if (i + 1 < parameters.Length) Console.Write(",");
            }

            Console.WriteLine(")");
        }

        int j;
        for (j = 0; j < constructors.Length; j++)
        {
            ParameterInfo[] parameters = constructors[j].GetParameters();
            if (parameters.Length == 2) break;
        }

        if (j == constructors.Length)
        {
            Console.WriteLine("No matching constructor was found.");
            return;
        }
        else
        {
            Console.WriteLine("Constructor with two parameters located");

            object[] arguments = new object[2];
            arguments[0] = 10;
            arguments[1] = 20;

            object o = constructors[j].Invoke(arguments);

            Console.WriteLine("Invoking methods of {0}", t.Name);

            MethodInfo[] methods = t.GetMethods();

            foreach (MethodInfo mi in methods)
            {
                ParameterInfo[] parameters = mi.GetParameters();

                if (mi.Name.CompareTo("Set") == 0 &&
                    parameters[0].ParameterType == typeof(int))
                {
                    object[] args = new object[2];
                    args[0] = 9;
                    args[1] = 18;
                    mi.Invoke(o, args);
                }

                else if (mi.Name.CompareTo("Set") == 0 &&
                    parameters[0].ParameterType == typeof(double))
                {
                    object[] args = new object[2];
                    args[0] = 1.5;
                    args[1] = 2.5;
                    mi.Invoke(o, args);
                }

                else if (mi.Name.CompareTo("Sum") == 0)
                {
                    int v = (int)mi.Invoke(o, null);
                    Console.WriteLine("Sum for {0} = {1}", o, v);
                }
            }
        }

    }
}

An assembly is loaded using the method Assembly.LoadFrom. The directories are navigated to obtain the debug form of the assembly Runtime8. Once an Assembly object is obtained, GetTypes() is used to obtain the Type structure of the class X contained in the other assembly. All the same logic contained in Runtime7 is then executed.