Practical DiskDiff in C#: Deeper Reflection

Examples in Chapter 22 showed how to use reflection to determine the attributes attached to a class. You can also use reflection to find all the types in an assembly or dynamically locate and call functions in an assembly. You can even use it to emit the .NET intermediate language on the fly to generate code that can be executed directly.

The documentation for the .NET CLR contains more details on using reflection.

1. Listing All the Types in an Assembly

The following example looks through an assembly and locates all the types in that assembly:

using System;

using System.Reflection;

enum MyEnum {

Val1,

Val2,

Val3

}

class MyClass

{

}

struct MyStruct

{

}

class Test {

public static void Main(String[] args)

{

// list all types in the assembly that is passed

// in as a parameter

Assembly a = Assembly.LoadFrom (args[0]);

Type[] types = a.GetTypes();

// look through each type, and write out some information

// about them.

foreach (Type t in types)

{

Console.WriteLine (“Name: {0}”, t.FullName);

Console.WriteLine (“Namespace: {0}”, t.Namespace);

Console.WriteLine (“Base Class: {0}”, t.BaseType.FullName);

}

}

}

If this example runs, passing the name of the .exe in, it will generate the following output:

Name: MyEnum Namespace:

Base Class: System.Enum Name: MyClass Namespace:

Base Class: System.Object Name: MyStruct Namespace:

Base Class: System.ValueType

Name: Test

Namespace:

Base Class: System.Object

2. Finding Members

This example lists the members of a type:

using System;

using System.Reflection;

class MyClass {

MyClass() {} static void Process()

{

}

public int DoThatThing(int i, Decimal d, string[] args)

{

return(55);

}

public int                    value = 0;

public float                       log = 1.0f;

public static int value2 = 44;

}

class Test {

public static void Main(String[] args)

{

// Iterate through the fields of the class

Console.WriteLine(“Fields of MyClass”);

type t = typeof (MyClass);

foreach (MemberInfo m in t.GetFields())

{

Console.WriteLine(“{0}”, m);

}

// and iterate through the methods of the class

Console.WriteLine(“Methods of MyClass”);

foreach (MethodInfo m in t.GetMethods())

{

Console.WriteLine(“{0}”, m);

foreach (ParameterInfo p in m.GetParameters())

{

Console.WriteLine(” Param: {0} {1}”, p.ParameterType, p.Name);

}

}

}

}

This example produces the following output:

Fields of MyClass

Int32 value

Single log

Int32 value2

Methods of MyClass

Void Finalize ()

Int32 GetHashCode ()

Boolean Equals (System.Object)

Param: System.Object obj

System.String ToString ()

Void Process ()

Int32 DoThatThing (Int32, System.Decimal, System.String[])

Param: Int32 i

Param: System.Decimal d

Param: System.String[] args

System.Type GetType ()

System.Object MemberwiseClone ()

For information on how to reflect over an enum, see Chapter 21.

When iterating over the methods in MyClass, the standard methods from object also show up.

3. Invoking Functions

In this example, reflection will open the names of all the assemblies on the command lines to search for the classes in them that implement a specific interface and then to create an instance of those classes and invoke a function on the instance.

This is useful to provide a late-bound architecture, where a component can be integrated with other components’ runtimes.

This example consists of four files. The first one defines the IProcess interface that will be searched for. The second and third files contain classes that implement this interface, and each is compiled to a separate assembly. The last file is the driver file; it opens the assemblies passed on the command line and searches for classes that implement IProcess. When it finds one, it instantiates an instance of the class and calls the Process() function.

3.1. IProcess.cs

IProcess defines that interface that you’ll search for:

// file=IProcess.cs namespace MamaSoft {

interface IProcess {

string Process(int param);

}

}

process1.cs

This is the process1.cs file:

// file=process1.cs

// compile with: csc /target:library process1.cs iprocess.cs

using System;

namespace MamaSoft {

class Processors IProcess {

Processor1() {}

public string Process(int param)

{

Console.WriteLine(“In Processor1.Process(): {0}”, param);

return(“Raise the mainsail! “);

}

}

}

This should be compiled with the following:

csc /target:library process1.cs iprocess.cs

process2.cs

This is the process2.cs file:

// file=process2.cs

// compile with: csc /target:library process2.cs iprocess.cs

using System;

namespace MamaSoft {

class Processor2: IProcess {

Processor2() {}

public string Process(int param)

{

Console.WriteLine(“In Processor2.Process(): {0}”, param);

return(“Shiver me timbers! “);

}

}

}

class Unrelated

{

}

This should be compiled with the following:

csc /target:library process2.cs iprocess.cs

driver.cs

This is the driver.cs file:

// file=driver.cs

// compile with: csc driver.cs iprocess.cs

using System;

using System.Reflection;

using MamaSoft;

class Test {

public static void ProcessAssembly(string aname)

{

Console.WriteLine(“Loading: {0}”, aname);

Assembly a = Assembly.LoadFrom (aname);

// walk through each type in the assembly

foreach (Type t in a.GetTypes())

{

// if it’s a class, it might be one that we want.

if (t.IsClass)

{

Console.WriteLine(” Found Class: {0}”, t.FullName);

// check to see if it implements IProcess

if (t.GetInterface(“IProcess”) == null)

continue;

// it implements IProcess. Create an instance

// of the object.

object o = Activator.CreateInstance(t);

// create the parameter list, call it,

// and print out the return value.

Console.WriteLine(“Calling Process() on {0}”, t.FullName);

object[] args = new object[] {55};

object result;

result = t.InvokeMember(“Process”,

BindingFlags.Default |

BindingFlags.InvokeMethod, null, o, args);

Console.WriteLine(”              Result: {0}”, result);

}

}

}

public static void Main(String[] args)

{

foreach (string arg in args)

ProcessAssembly(arg);

}

}

After this sample has been compiled, it can be run with the following:

process process1.dll process2.dll

This generates the following output:

Loading: process1.dll

Found Class: MamaSoft.Processor1

Calling Process() on MamaSoft.Processor1

In Processor1.Process(): 55

Result: Raise the mainsail!

Loading: process2.dll

Found Class: MamaSoft.Processor2

Calling Process() on MamaSoft.Processor2

In Processor2.Process(): 55

Result: Shiver me timbers!

Found Class: MamaSoft.Unrelated

For more information on generating code at execution time, see Chapter 32.

When calling functions with MemberInvoke(), any exceptions thrown will be wrapped in a TargetInvocationException, so the actual exception is accessed through the inner exception.

4. Dealing with Generics

Reflection has been fully updated to handle generics. Many new methods and properties deal with the potential that any given type or method parameter could be a generic type. The simplest way to determine if a particular type is generic is the new property of Type called IsGenericTypeDefinition. This property will return true only if the type is generic and the generic types haven’t been bound to a nongeneric type:

class Program {

static void Main(string[] args)

{

List<int> l = new List<int>();

//will be false

bool b1 = l.GetType().IsGenericTypeDefinition;

//will be true

bool b2 = l.GetType().GetGenericTypeDefinition().IsGenericTypeDefinition;

}

}

In this case, the IsGenericTypeDefinition returns false for the type List<int>, which is a type that doesn’t have any generic parameters. You can use the method GetGenericTypeDefinition() to get a reference from the constructed type List<int> back to the unbound generic type List<T>. The IsGenericTypeDefinition property returns true for this unbound generic type.

You can access the generic arguments for a type or method via the GetGenericArguments() method. Consider the following generic type:

class MyGenericClass<T> { }

You can display the generic parameter with the following code:

static void DumpGenericTypeParams(Type t)

{

if (t.IsGenericTypeDefinition)

{

foreach (Type genericType in t.GetGenericArguments())

{

Console.WriteLine(genericType.Name);

}

}

}

The output from this code when run against MyGenericClass<T> is simply as follows:

T

Although simply dumping out the type name may not be overly useful, various reflection methods exist to access information such as the constraints that apply to the generic parameters (Type.GetGenericParameterConstraints()) and to bind generic parameters to nongeneric types (Type.BindGenericParameters()).

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *