Click here to Skip to main content
15,124,055 members
Articles / Programming Languages / C#
Article
Posted 1 Apr 2016

Tagged as

Stats

46.4K views
982 downloads
79 bookmarked

C# Lectures - Lecture 7: Reflection by C# example

Rate me:
Please Sign up or sign in to vote.
4.98/5 (31 votes)
29 Aug 2016CPOL7 min read
This is 7th article from my series about C# and it is about Reflection

Full Lectures Set


Introduction

This is a 7th article from my series of lectures about C#. This time I'm going to talk about .NET technology called reflection. This is a very efficient technology for the software model that is developed by several companies where one developed server\host site and another client\s. Such a plug-in model is very widely used, when host is looking for available clients and discovers them while runtime. Reflection is applicable when you need to discover and\or instantiate type without knowing anything about it while compilation. You should be aware that from .NET 4.5 Reflection API were renewed and it was build more efficient bunch of functionality than previous one that was heavy and slow as used many resources inefficiently.

Refclection abilities and areas of usage

Classes from System.Reflection together with System.Type provide us with following major areas of usage:

  •  Get information about loaded:

    1. Assemblies

    1. Types

      1. Classes

      2. Interfaces

      1. Value types

  • Instantiate types while runtime

  • Invoke access to types in loaded assembly

Reflection actually is rarely used in usual applications and this indeed very powerful mechanism is mostly used by Microsoft itself for Visual Studio work and its frameworks. Developers use reflection when they need to discover some third party binaries or they need to load specific type from specific assembly. You can imagine situation when for example some host receives third party plugins and basing on user decision plugin should be discovered for specific type.

Main disadvantage of reflection is that it works really slow. You shouldn't use reflection if there is workaround about this. When you use reflection you are based on strings representative of things and behind the scene your assembly's metadata is constantly monitored for sting names of things. This is resource consuming operation.

Code for reflection example

For this article I've defined following classes inside my assembly, we will discover this data programmatically in sections below:

C#
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]//I want my attribute to be applied to anything where it is possible - AttributeTargets.All
//also my attribute can be inherited - Inherited=true
//also my attribute can't be applied several times for one element - AllowMultiple=false
public sealed class MyOwnExcitingAttribute : System.Attribute
{
    private string m_StringData;
    private int m_IntegerData;

    public MyOwnExcitingAttribute()
    {
        m_StringData = "default value";
    }
    public MyOwnExcitingAttribute(string s)
    {
        m_StringData = s;
    }
    public int IntegerData
    {
        get { return m_IntegerData; }
        set { m_IntegerData = value; }
    }
    public string StringData
    {
        get { return m_StringData; }//we encapsulate only reading, you can set this variable only in constructor
    }
}
public class AttributeUser
{
    [MyOwnExciting("custom value applied to constructor", IntegerData = 2)]
    public AttributeUser()
    {
    }
    [MyOwnExciting("custom value applied to function", IntegerData = 5)]
    public void FunctionThatDoSomething()
    {
    }
    [MyOwnExciting("custom value applied to member", IntegerData = 10)]
    public int m_IntData;
    [MyOwnExciting(IntegerData = 10)]//here string data will have "default value"
    private int m_IntData2;

    public int IntData2
    {
        get { return m_IntData2; }
        set { m_IntData2 = value; }
    }
}

Reflection at assembly level

In this section we will review how reflection can be used to discover assembly. When CLR loads any assembly to your application it uses method Load from System.Reflection.Assembly class. This method is equivalent to LoadLibrary from Win32 API. To call method load you need to provide to it information about assembly and to get it you need to use special utilities. This method has several overloads and besides this class Assembly has several functions that provide you with alternative way to load assembly. For example to load assembly from specific path you need to call function Assembly.LoadFrom. For this article I've built all the sources in one assembly and will discover itself while runtime. To do this I use Assembly.GetExecutingAssembly method:

 

Assembly current = Assembly.GetExecutingAssembly();

 

Once you loaded assembly you have a variable that holds its reference and using which we may discover it. Class Assembly has list of properties and methods that you can call to know what is defined and applied to specific assembly. Code below demonstrates how I extract assembly data programmatically:

C#
//-------------------------------------ASSEMBLY------------------------------
Assembly currentAssembly = Assembly.GetExecutingAssembly();
Assembly loadedAssembly = Assembly.LoadFrom(@"..\..\..\ReflectionSample\bin\Debug\ReflectionSample.dll");
//properties
Console.WriteLine(currentAssembly.CodeBase);//shows assembly binary file path
//below I output only data about my own attribute applied for this assembly (look for AssemblyInfo.cs)
var customAttributes = currentAssembly.CustomAttributes;
foreach(CustomAttributeData attribute in customAttributes)
{
    string s = attribute.AttributeType.GetType().Name;
    if (attribute.AttributeType == typeof(MyOwnExcitingAttribute))
    {
        Console.WriteLine(attribute.AttributeType);
        Console.WriteLine(attribute.Constructor);
        Console.WriteLine(attribute.ConstructorArguments);
        Console.WriteLine(attribute.NamedArguments);
    }
}
//below I read all types defined in my assembly
Console.WriteLine("\n Assemblies types are: " + currentAssembly.EntryPoint);
var definedTypes = currentAssembly.DefinedTypes;
foreach (Type t in definedTypes)
{
    Console.WriteLine("Type namespace = " + t.Namespace);
    Console.WriteLine("Type name = " + t.Name);
    Console.WriteLine("Is type public =" + t.IsPublic);
}
//outputs entry point
Console.WriteLine("\n Assembly entry point = " + currentAssembly.EntryPoint);
//below I read all types that are exported by my assembly
Console.WriteLine("\n Assemblies types that are exported are: ");
var exportedTypes = currentAssembly.ExportedTypes;
foreach (Type t in exportedTypes)
{
    Console.WriteLine("Type namespace = " + t.Namespace);
    Console.WriteLine("Type name = " + t.Name);
    Console.WriteLine("Is type public =" + t.IsPublic);
}
//below I read all types that are exported by my assembly
Console.WriteLine("\n Assembliy loaded only for reflection: " + currentAssembly.ReflectionOnly);

//below I read all modules in my assembly
Console.WriteLine("\n Assemblies modules are: ");
var modules = currentAssembly.Modules;
Module myModule = null;
foreach (Module m in modules)
{
    Console.WriteLine("Scope name = " + m.ScopeName);
    Console.WriteLine("Type name = " + m.Name);
    Console.WriteLine("Assembly =" + m.Assembly);
    myModule = m;
}

In general using Assembly class you can:

  • Get list of all types in it
  • Get list of exported types
  • Get list of attributes applied to assembly
  • Get list of modules in assembly
  • Get different assembly properties such as: name, code base, entry point, loaded for only reflection or not, etc.
  • Load an assembly by many different ways
  • Instantiate specified type from this assembly
  • Get assembly for specific type
  • Etc.
  • Etc.
  • Etc.

Class Assembly is full of useful functionality and if you loaded some .NET assembly using this class you will be able to discover it and everything that is declared in it.

Reflection at module level

Once you loaded assembly and discover it using reflection, you get modules that it contains and discover them as well. To get information about module you can use System.Reflection.Module class. Code below demonstrates some reflection examples at module's level:

C#
//-------------------------------------MODULE------------------------------
Console.WriteLine("Module's assembly is: " + myModule.Assembly);
customAttributes = myModule.CustomAttributes;
foreach (CustomAttributeData attribute in customAttributes)
{
        Console.WriteLine(attribute.AttributeType);
        Console.WriteLine(attribute.Constructor);
        Console.WriteLine(attribute.ConstructorArguments);
        Console.WriteLine(attribute.NamedArguments);
}
Console.WriteLine("Module's fully qualified name is: " + myModule.FullyQualifiedName);
Console.WriteLine("Module's name: " + myModule.Name);
Console.WriteLine("Module's scope name: " + myModule.ScopeName);

//i'm looking for specific type in my module
var queriedType = myModule.GetType("_07_Reflection.AttributeUser");
if (queriedType != null)
{
    Console.WriteLine(queriedType.FullName);
}

Using Module class you can get:

  • Its assembly name
  • Fully qualified name
  • Metatada stream version
  • Metadata token
  • Module's handle
  • Version ID
  • Scope name
  • Name
  • Custom attributes that module contains
  • Types of the module
  • Global methods of the module //not for C# at least I don't know how to define global method in C#

 

There are also different methods in Type module that gives you ability to work with it or data that are inside module. There are a set of methods that gives you ability to query for types, methods and fields defined in module.

Keep a running update of any changes or improvements you've made here.

Reflection at type level

There are set of classes that you can use to get information about specific types:

  •  System.Reflection.ConstructorInfo - to discover information about constructor: name, parameters, visibility, implementation details (abstract or virtual)
  •  System.Reflection.MethodInfo - to discover information about methods: name, parameters, visibility, implementation details (abstract or virtual)
  • System.Reflection.FieldInfo - to get information about field: name, visibility, implementation details (static or not)
  • System.Reflection.EventInfo - to get information about event: name, event-handler data type, custom attributes, declaring type
  • System.Reflection.PropertyInfo - to get information about property: name, data type, declaring type, reflected type, read-only or writable status of the property
  • System.Reflection.ParameterInfo - to get information about parameter: name, data type, is parameter input or output, position in method signature

Code below demonstrates usage of some of the described classes to discover different information about internal type organization:

C#
//-------------------------------------TYPE------------------------------
Console.WriteLine("------------DISCOVERING TYPE: " + queriedType.Name + "-----------------");
Console.WriteLine("--Constructors details");
var constructorsInfo = queriedType.GetConstructors();
foreach (ConstructorInfo ci in constructorsInfo)
{
    Console.WriteLine("Name = " + ci.Name);
    Console.WriteLine("Member type = " + ci.MemberType);
    Console.WriteLine("Is virtual = " + ci.IsVirtual);
    Console.WriteLine("Is static = " + ci.IsStatic);
    Console.WriteLine("Is public = " + ci.IsPublic);
}
Console.WriteLine("--Methods details");
var methodsInfo = queriedType.GetMethods();
foreach (MethodInfo mi in methodsInfo)//here we can see also methods derieved from System.Object
{
    Console.WriteLine("Name = " + mi.Name);
    Console.WriteLine("Member type = " + mi.MemberType);
    Console.WriteLine("Is virtual = " + mi.IsVirtual);
    Console.WriteLine("Is static = " + mi.IsStatic);
    Console.WriteLine("Is public = " + mi.IsPublic);
}
Console.WriteLine("--Fields details");
var fieldsInfo = queriedType.GetFields();//here we can see only public fields
foreach (FieldInfo fi in fieldsInfo)
{
    Console.WriteLine("Name = " + fi.Name);
    Console.WriteLine("Member type = " + fi.MemberType);
    Console.WriteLine("Is static = " + fi.IsStatic);
    Console.WriteLine("Is public = " + fi.IsPublic);
}
Console.WriteLine("--Properties details");
var propInfo = queriedType.GetProperties();
foreach (PropertyInfo pi in propInfo)
{
    Console.WriteLine("Name = " + pi.Name);
    Console.WriteLine("Member type = " + pi.MemberType);
    Console.WriteLine("Can read = " + pi.CanRead);
    Console.WriteLine("Can write = " + pi.CanWrite);
}

 

Reflection is not only discovery. Instantiating and invoking using reflection

Reflection provides us with ability not only to discover content of managed assemblies, but also instantiate and use it while runtime. There are several ways to do this:

  • Late implicit binding. This approach is applicable for languages such as Visual Basic and JScript and less relevant to this topic, but I wanted to mention it here as this is important to know about reflection. Binding is the process of locating implementation basing on unique name. When this happens on run time, not compile time, this is called late binding. Visual Basic allows you to use implicit late binding in your code; the Visual Basic compiler calls a helper method that uses reflection to obtain the object type. The arguments passed to the helper method cause the appropriate method to be invoked at run time.
  • Custom binding. In addition to late implicit building that is done by compilers reflection can be used explicitly in code to do late binding.  CLR supports many languages and rules for late binding are different for all these languages. We will review samples of late building using C#. With reflection you can load assembly at runtime, explore it to find type that you require and then invoke its methods or access its fields or properties. This technique is useful when you don't know type of object at compile type and its type is identified later while program execution.

Once you have reference to object that is derived from Type, you have several ways to instantiate it:

  • CreateInstance from System.Activator - this method has set of overloads that gives us ability to create object. I use the one that takes Type as parameter in my sample below.
  • CreateInstanceFrom from System.Activator - this method is similar to the one above, but none of its overloads can take parameter Type, all them require string data for assembly and type.
  • Invoke from System.Reflection.ConstructorInfo

Code below demonstrates things described in this section:

C#
//-------------------------------------INSTATIATING------------------------------
Console.WriteLine("------------INSTANTIATING TYPE: -----------------");
Console.WriteLine("--System.Activator.CreateInstnce");
var Instance1 = System.Activator.CreateInstance(queriedType);
Console.WriteLine(Instance1.GetType().FullName);
var Instance2 = constructorsInfo[0].Invoke(null);
Console.WriteLine(Instance2.GetType().FullName);

Building types while runtime.

Reflection has a namespace called System.Reflection.Emit. This namespaces contains classes that give you ability to emit metadata at Microsoft Intermediate Language and optionally generate PE file at disc. Users of Emit classes are script engines and compilers.  I will not dive into details of System.Reflection.Emit stuff in this article as this is specific topic that requires separate article to be created, but I wanted to you be aware about such a thing in Reflection namespace.

Sources:

Jeffrey Richter - CLR via C#
Andrew Troelsen - Pro C# 5.0 and the .NET 4.5 Framework

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sergey Kizyan
Architect
Ukraine Ukraine
Working on different projects and technologies from web to low level core programming, from scripting languages to C++. With all this stuff my personal opinion is that C#,.NET and Microsoft environment is the best thing programmer may have. Basing on it I prefer to publish only about my favorite technologies that are .NET and Azure now.

P.S. Looking for partnership and cooperation in outsourcing.

Comments and Discussions

 
Questionhelp Pin
Member 125854928-Aug-16 11:37
MemberMember 125854928-Aug-16 11:37 
AnswerRe: help Pin
Sergey Kizyan9-Aug-16 3:29
professionalSergey Kizyan9-Aug-16 3:29 
GeneralRe: help Pin
Member 125854929-Aug-16 4:42
MemberMember 125854929-Aug-16 4:42 
GeneralRe: help Pin
Sergey Kizyan9-Aug-16 4:44
professionalSergey Kizyan9-Aug-16 4:44 
GeneralRe: help Pin
Member 125854929-Aug-16 5:03
MemberMember 125854929-Aug-16 5:03 
GeneralRe: help Pin
Sergey Kizyan9-Aug-16 5:06
professionalSergey Kizyan9-Aug-16 5:06 
GeneralRe: help Pin
Member 125854929-Aug-16 5:16
MemberMember 125854929-Aug-16 5:16 
GeneralRe: help Pin
Member 125854929-Aug-16 5:18
MemberMember 125854929-Aug-16 5:18 
GeneralRe: help Pin
Sergey Kizyan9-Aug-16 5:31
professionalSergey Kizyan9-Aug-16 5:31 
GeneralRe: help Pin
Member 125854929-Aug-16 5:37
MemberMember 125854929-Aug-16 5:37 
GeneralRe: help Pin
Sergey Kizyan9-Aug-16 5:41
professionalSergey Kizyan9-Aug-16 5:41 
GeneralRe: help Pin
Member 125854929-Aug-16 6:01
MemberMember 125854929-Aug-16 6:01 
GeneralRe: help Pin
Sergey Kizyan9-Aug-16 6:03
professionalSergey Kizyan9-Aug-16 6:03 
GeneralMy vote of 5 Pin
Santhakumar M5-May-16 22:55
professionalSanthakumar M5-May-16 22:55 
GeneralLearning Pin
NextGDesign5-May-16 1:29
MemberNextGDesign5-May-16 1:29 
GeneralRe: Learning Pin
Sergey Kizyan5-May-16 2:52
professionalSergey Kizyan5-May-16 2:52 
PraiseGood Article Pin
konakondla4-Apr-16 9:50
Memberkonakondla4-Apr-16 9:50 
GeneralRe: Good Article Pin
Sergey Kizyan4-Apr-16 12:00
professionalSergey Kizyan4-Apr-16 12:00 
Appreciate your feedback

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.