Blog Archives

Reflection (part 3, advanced, professional), plugin

We are progressing further.

Reflection tells us all we need for plugins. Once we know the assembly we can examine it like a poor frog on the dissection table. If the dissector cannot find something, then it is not the fault of the frog. Sorry for the macabre metaphor, I could not resist my ugly thoughts.

First of all we need an assembly. In the Solution Explorer right-click your Project. The tab-menu shows (Application, Build, Build Events, Debug …). Select Application. The first entry is “Assembly name:”. You can enter your prefered assembly name here. Let’s use “TestAssembly” for our example. Now the compiler will create an assembly with the name TestAssembly.

A short test program gives you an idea where your assembly is stored and what its name is.

Assembly lAssembly = Assembly.GetCallingAssembly();
Console.WriteLine(lAssembly.Location);  // prints the full path of your assembly
Console.WriteLine(lAssembly.FullName);

You can load assemblies into applications. We are concentrating on plugins today.
In theory you can get all required information to eg. call methods or properties via reflection itself. There is no need for extra definition files. But you can also make your life easier and predefine signatures, which are used by both the application and the plugin. Of course you are implementing constraints that reduce the flexibility, but on the other hand you reduce the error-proneness and save a lot of debugging time.
So we are going to use an interface to predefine signatures. It is important that you do not copy the interface source code into both projects. You should compile the interface into a DLL-library and access these definitions from outside in the two assemblies. Otherwise the compilers generate different identifiers (see GUID) and two equal interfaces cannot be recognized as the same type.

To clearly show the independence I want you to open Visual Studio three times. Please do not create three projects in one single solution.

Visual Studio 1

Create a library project and paste the following code. We don’t need any “using” or “namespace”. Five lines of code are enough in our example.

public interface IPlugin {
    string Name { get; }
    int Born { get; }
    double DoSomething(double d, string s);
} // interface

Compile the project and move the library DLL-file to your preferred location.

Visual Studio 2

Add a reference to our interface library. (Right click on References in your Solution Explorer. Add Reference, then browse for the library file.)
We are creating the plugin now. The method Test() prints a path that you will need in the source code for the calling application (just to make your life easier).

public class MyPlugin1 : IPlugin {
    public string Name { get { return "Diana Frances"; } }
    public int Born { get { return 1961; } }
    public double DoSomething(double d, string s) { return 1.0; }
} // class

public class MyPlugin2 : IPlugin {
    public string Name { get { return "Jeanne d'Arc"; } }
    public int Born { get { return 1412; } }
    public double DoSomething(double d, string s) { return 1.0; }
} // class

public static class TestClass {
    public static void Test() {
        Assembly lPluginAssembly = Assembly.GetCallingAssembly();
        // Assembly lPluginAssembly = Assembly.Load("TestAssembly");  // alternative
        Console.WriteLine(lPluginAssembly.Location);
        Console.WriteLine(lPluginAssembly.FullName);
        Console.ReadLine();
    } //
} // class

Visual Studio 3

The projects in Visual Studio 1 and Visual Studio 2 are compiled. You can close these two programs.
Once again add a reference to our interface library. In below example code you need to replace a path by the one, which was printed out in Visual Studio 2. (in line Assembly lPluginAssembly = Assembly.LoadFile(@”xxxxxx\TestAssembly.exe”);)

static void Main(string[] args) {
    Assembly lPluginAssembly = Assembly.LoadFile(@"xxxxxx\TestAssembly.exe");
    //Assembly lPluginAssembly = Assembly.Load("TestAssembly");   => if loaded already

    var lPlugins = from t in lPluginAssembly.GetTypes()
                    where typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface
                    select t;

    foreach (Type t in lPlugins) {
        IPlugin lPlugin = Activator.CreateInstance(t) as IPlugin;
        Console.WriteLine(lPlugin.Name + " was born in " + lPlugin.Born);
        double d = lPlugin.DoSomething(2.0, "hello");
    }
    Console.ReadLine();
} // main

output example:
Diana Frances was born in 1961
Jeanne d’Arc was born in 1412

The program loaded the assembly, created instances for each class and called the properties and the method. This basically is all you need for Plugins.

Of course there are also dirty ways to invoke plugin methods. One is:

Assembly lPluginAssembly = Assembly.LoadFile(@"xxxxxx\TestAssembly.exe");
Type t = lPluginAssembly.GetType("MyNamespaceName.MyPlugin1");
MethodInfo m = t.GetMethod("DoSomething");
double d = (double)m.Invoke(Activator.CreateInstance(t), new object[] { 2.0, "hello" });                       

Reflection (part 1, basics, advanced), types and generic types

I explained generic types yesterday. Discussing reflection is a bit early on this post. I will concentrate on some basic and slightly advanced material about generics and nullables.

Reflection is used to obtain type information at run-time.
C# offers the typeof() keyword for types and the GetType() method for objects.
The is keyword uses both and follows the pattern: object is type

AnyClass c = new AnyClass();
Type a1 = typeof(int);           // typeof(type)
Type a2 = c.GetType();           // object.GetType()
bool b = c is int;               // object is type

The is keyword returns true if an instance is in the inheritance tree. This results in a very specific behaviour:

class Animal { }
class Cat : Animal { }

public static void Generics6() {
    // easy going
    Animal lAnimal = new Animal();
    Console.WriteLine(lAnimal.GetType() == typeof(Animal)); // true 
    Console.WriteLine(lAnimal is Animal);                   // true 
    Console.WriteLine(lAnimal.GetType() == typeof(Cat));    // false

    // and now it gets tricky
    Animal lCat = new Cat();
    Console.WriteLine(lCat.GetType() == typeof(Animal)); // false !
    Console.WriteLine(lCat is Animal);                   // true ! 
    Console.WriteLine(lCat.GetType() == typeof(Cat));    // true
} //

The typeof(T) also has its own behaviour. It is does not care what true instance type it is looking at. It returns the result of the last cast operation. Here is an example program to highlight this fact:

static void Print<T>(T t, string xInfo) {
    Console.WriteLine(xInfo.PadRight(22) + "| typeof(T) " + typeof(T).Name.PadRight(7) + "| GetType(): " + t.GetType().Name.PadRight(7) + "| is Cat: " + (t is Cat).ToString().PadRight(6) + "| is Animal: " + (t is Animal));
} //

public static void Confusing() {
    Animal lAnimal = new Cat();
    Cat lCat = new Cat();

    Print(lCat, "Print(lCat)");
    Print<Cat>(lCat, "Print<Cat>(lCat)");

    Print(lAnimal, "Print(lAnimal)"); 
    Print<Animal>(lAnimal, "Print<Animal>(lAnimal)");
            
    Print<Animal>(lCat, "Print<Animal>(lCat)");
    Print((Animal)lCat, "Print((Animal)lCat)");

    //Print<Cat>(lAnimal); // compiler error    
} //

example output in a table:

Command typeof(T) GetType() Is Cat? Is Animal?
Print(lCat)

Cat

Cat

True

True

Print<Cat>(lCat)

Cat

Cat

True

True

Print(lAnimal)

Animal

Cat

True

True

Print<Animal>(lAnimal)

Animal

Cat

True

True

Print<Animal>(lCat)

Animal

Cat

True

True

Print((Animal)lCat)

Animal

Cat

True

True

Walk through the example of a generic method:

private static void Generics4<T>(T t) {
    Type lType = typeof(T);

    Console.WriteLine("Analysing variable " + t + Environment.NewLine);

    Console.WriteLine("using typeof()");
    if (lType == typeof(int)) Console.WriteLine(typeof(T).ToString() + " is an integer");
    else if (lType == typeof(string)) Console.WriteLine(typeof(T).ToString() + " is a string");

    Console.WriteLine("\nusing the \"is\" keyword");
    if (t is int) Console.WriteLine(typeof(T).ToString() + " is an integer");
    else if (t is string) Console.WriteLine(typeof(T).ToString() + " is a string");

    Console.WriteLine("\nusing GetType()");
    if (t.GetType() == typeof(int)) Console.WriteLine(typeof(T).ToString() + " is an integer");
    else if (t.GetType() == typeof(string)) Console.WriteLine(typeof(T).ToString() + " is a string");
} //

public static void Generics5() {
    Generics4(5);
    Console.WriteLine();
    Generics4("hello");
    Console.WriteLine();
    int? i = 666;
    Generics4(i);
    Console.ReadLine();
} //

Analysing variable 5

using typeof()
System.Int32 is an integer

using the “is” keyword
System.Int32 is an integer

using GetType()
System.Int32 is an integer

Analysing variable hello

using typeof()
System.String is a string

using the “is” keyword
System.String is a string

using GetType()
System.String is a string

Analysing variable 666

using typeof()

using the “is” keyword
System.Nullable`1[System.Int32] is an integer

using GetType()
System.Nullable`1[System.Int32] is an integer

The Type class offers far more information about the object we are using.

class GenericClass7<T> { public void HelloWorld() { } }
private class AnyClass { }

public static void Generics7<T>(T t) {
    Type lType = typeof(T);

    Console.WriteLine("value".PadRight(30) + t);
    Console.WriteLine("IsPublic".PadRight(30) + lType.IsPublic);
    Console.WriteLine("IsGenericParameter".PadRight(30) + lType.IsGenericParameter);
    Console.WriteLine("IsGenericType".PadRight(30) + lType.IsGenericType);
    Console.WriteLine("IsGenericTypeDefinition".PadRight(30) + lType.IsGenericTypeDefinition);
    Console.WriteLine("IsValueType".PadRight(30) + lType.IsValueType);
    Console.WriteLine("Name".PadRight(30) + lType.Name);
    Console.WriteLine("BaseTypeName".PadRight(30) + lType.BaseType.Name);
    foreach (MemberInfo lMember in lType.GetMembers()) {
        Console.WriteLine("Member".PadRight(30) + lMember.Name);
    }
    Console.WriteLine(string.Empty.PadRight(50, '-'));
} //

public static void RunTest() {
    Console.WriteLine("integer:\n");
    Generics7(1);
    Console.WriteLine("nullable integer:\n");
    Generics7(new Nullable<int>(2));
    Console.WriteLine("string:\n");
    Generics7("hello");
    Console.WriteLine("class:\n");
    Generics7(new AnyClass());
    Console.WriteLine("generic class:\n");
    Generics7(new GenericClass7<double>());

    Console.WriteLine();
} //

example output:

integer:

value 1
IsPublic True
IsGenericParameter False
IsGenericType False
IsGenericTypeDefinition False
IsValueType True
Name Int32
BaseTypeName ValueType
Member CompareTo
Member CompareTo
Member Equals
Member Equals
Member GetHashCode
Member ToString
Member ToString
Member ToString
Member ToString
Member Parse
Member Parse
Member Parse
Member Parse
Member TryParse
Member TryParse
Member GetTypeCode
Member GetType
Member MaxValue
Member MinValue
————————————————–
nullable integer:

value 2
IsPublic True
IsGenericParameter False
IsGenericType True
IsGenericTypeDefinition False
IsValueType True
Name Nullable`1
BaseTypeName ValueType
Member get_HasValue
Member get_Value
Member GetValueOrDefault
Member GetValueOrDefault
Member Equals
Member GetHashCode
Member ToString
Member op_Implicit
Member op_Explicit
Member GetType
Member .ctor
Member HasValue
Member Value
————————————————–
string:

value hello
IsPublic True
IsGenericParameter False
IsGenericType False
IsGenericTypeDefinition False
IsValueType False
Name String
BaseTypeName Object
Member Join
Member Join
Member Join
Member Join
Member Join
Member Equals
Member Equals
Member Equals
Member Equals
Member Equals
Member op_Equality
Member op_Inequality
Member get_Chars
Member CopyTo
Member ToCharArray
Member ToCharArray
Member IsNullOrEmpty
Member IsNullOrWhiteSpace
Member GetHashCode
Member get_Length
Member Split
Member Split
Member Split
Member Split
Member Split
Member Split
Member Substring
Member Substring
Member Trim
Member TrimStart
Member TrimEnd
Member IsNormalized
Member IsNormalized
Member Normalize
Member Normalize
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member CompareTo
Member CompareTo
Member CompareOrdinal
Member CompareOrdinal
Member Contains
Member EndsWith
Member EndsWith
Member EndsWith
Member IndexOf
Member IndexOf
Member IndexOf
Member IndexOfAny
Member IndexOfAny
Member IndexOfAny
Member IndexOf
Member IndexOf
Member IndexOf
Member IndexOf
Member IndexOf
Member IndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOfAny
Member LastIndexOfAny
Member LastIndexOfAny
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member PadLeft
Member PadLeft
Member PadRight
Member PadRight
Member StartsWith
Member StartsWith
Member StartsWith
Member ToLower
Member ToLower
Member ToLowerInvariant
Member ToUpper
Member ToUpper
Member ToUpperInvariant
Member ToString
Member ToString
Member Clone
Member Trim
Member Insert
Member Replace
Member Replace
Member Remove
Member Remove
Member Format
Member Format
Member Format
Member Format
Member Format
Member Copy
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Intern
Member IsInterned
Member GetTypeCode
Member GetEnumerator
Member GetType
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member Chars
Member Length
Member Empty
————————————————–
class:

value ConsoleApplication1.CGenerics+AnyClass
IsPublic False
IsGenericParameter False
IsGenericType False
IsGenericTypeDefinition False
IsValueType False
Name AnyClass
BaseTypeName Object
Member ToString
Member Equals
Member GetHashCode
Member GetType
Member .ctor
————————————————–
generic class:

value ConsoleApplication1.CGenerics+GenericClass7`1[System.Double]
IsPublic False
IsGenericParameter False
IsGenericType True
IsGenericTypeDefinition False
IsValueType False
Name GenericClass7`1
BaseTypeName Object
Member HelloWorld
Member ToString
Member Equals
Member GetHashCode
Member GetType
Member .ctor
————————————————–

I guess there could be hundreds of posts just about reflection. This short explanation should be enough for today. Basically we would start digging ourselves deep into the System.Reflection.Emit namespace. We will touch this topic again when appropriate.