Chapter 6. Reflection and Dynamic Programming

6.0 Introduction

Reflection is the mechanism provided by the .NET Framework to allow you to inspect how a program is constructed. Using reflection, you can obtain information such as the name of an assembly and what other assemblies a given assembly imports. You can even dynamically call methods on an instance of a type in a given assembly. Reflection also allows you to create code dynamically and compile it to an in-memory assembly or to build a symbol table of type entries in an assembly.

Reflection is a very powerful feature of the Framework and, as such, is guarded by the runtime. The ReflectionPermission must be granted to assemblies that are going to access the protected or private members of a type. If you are going to access only the public members of a public type, you will not need to be granted the ReflectionPermission. Code Access Security (CAS) has only two permission sets that give all reflection access by default: FullTrust and Everything. The LocalIntranet permission set includes the ReflectionEmit privilege, which allows for emitting metadata and creating assemblies, and the MemberAccess privilege, which allows for performing dynamic invocation of methods on types in assemblies.

In this chapter, you will see how you can use reflection to dynamically invoke members on types, figure out all of the assemblies a given assembly is dependent on, and inspect assemblies for different types of information. Reflection is a great way to understand how things are put together in .NET, and this chapter provides a starting point.

This chapter will also cover the dynamic keyword in C#, which is supported by the Dynamic Language Runtime (DLR) in .NET. It is used to help extend C# to identify the type of an object at runtime instead of statically at compile time, and to support dynamic behavior. To use these features, you need to reference the System.Dynamic assembly and namespace.

The DLR was introduced to support the following use cases:

  • Porting other languages (like Python and Ruby) to .NET

  • Enabling dynamic features in static languages (like C# and Visual Basic)

  • Enabling more sharing of libraries between languages

  • Caching binding operations (like Reflection) to improve performance instead of determining everything at runtime each time

The DLR provides three main services:

  • Expression trees (to represent language semantics such as those used in LINQ)

  • Call site caching (caches the characteristics of the operation the first time it is performed)

  • Dynamic object interoperability (through the use of IDynamicMetaObjectProvider, DynamicMetaObject, DynamicObject, and ExpandoObject)

The three main constructs provided to do dynamic programming in C# are the dynamic type (an object that is not bound by compile-time checking), the ExpandoObject class (used to construct or deconstruct the members of an object at runtime), and the DynamicObject class (a base class for adding dynamic behavior to your own objects). All three constructs are demonstrated in this chapter.

6.1 Listing Referenced Assemblies

Problem

You need to determine each assembly imported by a particular assembly. This information can show you if this assembly is using one or more of your assemblies or if it is using another specific assembly.

Solution

Use the Assembly.GetReferencedAssemblies method, as shown in Example 6-1, to obtain the imported assemblies of a particular assembly.

Example 6-1. Using the Assembly.GetReferencedAssemblies method
public static void BuildDependentAssemblyList(string path,
    StringCollection assemblies)
{
    // maintain a list of assemblies the original one needs
    if(assemblies == null)
        assemblies = new StringCollection();

    // have we already seen this one?
    if(assemblies.Contains(path)==true)
        return;

    try
    {
        Assembly asm = null;

        // look for common path delimiters in the string
        // to see if it is a name or a path
        if ((path.IndexOf(@"", 0, path.Length, StringComparison.Ordinal) != -1) ||
            (path.IndexOf("/", 0, path.Length, StringComparison.Ordinal) != -1))
        {
            // load the assembly from a path
            asm = Assembly.LoadFrom(path);
        }
        else
        {
            // try as assembly name
            asm = Assembly.Load(path);
        }

        // add the assembly to the list
        if (asm != null)
            assemblies.Add(path);

        // get the referenced assemblies
        AssemblyName[] imports = asm.GetReferencedAssemblies();

        // iterate
        foreach (AssemblyName asmName in imports)
        {
            // now recursively call this assembly to get the new modules
            // it references
            BuildDependentAssemblyList(asmName.FullName, assemblies);
        }
    }
    catch (FileLoadException fle)
    {
        // just let this one go...
        Console.WriteLine(fle);
    }
}

This code returns a StringCollection containing the original assembly, all imported assemblies, and the dependent assemblies of the imported assemblies.

If you ran this method against the assembly C:CSharpRecipesinDebugCSharpRecipes.exe, you’d get the following dependency tree:

Assembly C:CSharpRecipesinDebugCSharpRecipes.exe has a dependency tree of
these assemblies :

    C:CSharpRecipesinDebugCSharpRecipes.exe
    mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    System.Configuration, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    System.Data.SqlXml, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
    System.Security, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b03f5f7f11d50a3a
    System.Core, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b77a5c561934e089
    System.Numerics, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b77a5c561934e089
    System.Messaging, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.DirectoryServices, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Transactions, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
    System.EnterpriseServices, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
    System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
    System.Drawing, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b03f5f7f11d50a3a
    System.Data, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b77a5c561934e089
    System.Web.RegularExpressions, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Design, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b03f5f7f11d50a3a
    System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
    Accessibility, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b03f5f7f11d50a3a
    System.Runtime.Serialization.Formatters.Soap, Version=4.0.0.0, 
    Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Deployment, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b03f5f7f11d50a3a
    System.Data.OracleClient, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
    System.Drawing.Design, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Web.ApplicationServices, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
    System.ComponentModel.DataAnnotations, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
    System.DirectoryServices.Protocols, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Runtime.Caching, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.ServiceProcess, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Configuration.Install, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Runtime.Serialization, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
    System.ServiceModel.Internals, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
    SMDiagnostics, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b77a5c561934e089
    System.Web.Services, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    Microsoft.Build.Utilities.v4.0, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    Microsoft.Build.Framework, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Xaml, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b77a5c561934e089
    Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    NorthwindLinq2Sql, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=fe85c3941fbcc4c5
    System.Data.Linq, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
    System.Xml.Linq, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
    EntityFramework, Version=6.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
    Microsoft.CSharp, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a
    System.Dynamic, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b03f5f7f11d50a3a
    System.Data.DataSetExtensions, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089

Discussion

Obtaining the imported types in an assembly is useful in determining what assemblies another assembly is using. This knowledge can greatly aid you in learning to use a new assembly. This method can also help you determine dependencies between assemblies for shipping purposes or to perform compliance management if you are restricted from using or exporting certain types of assemblies.

The GetReferencedAssemblies method of the System.Reflection.Assembly class obtains a list of all the imported assemblies. This method accepts no parameters and returns an array of AssemblyName objects instead of an array of Types. The AssemblyName type is made up of members that allow access to the information about an assembly, such as the name, version, culture information, public/private key pairs, and other data.

To call the BuildDependentAssemblyList method on the current executable, run this example code:

string file = GetProcessPath();

StringCollection assemblies = new StringCollection();

ReflectionAndDynamicProgramming.BuildDependentAssemblyList(file,assemblies);

Console.WriteLine($"Assembly {file} has a dependency tree of these
assemblies:{Environment.NewLine}");
foreach(string name in assemblies)
{
    Console.WriteLine($"	{name}{Environment.NewLine}");
}

GetProcessPath, shown here, returns the current path to the process executable:

private static string GetProcessPath()
{
    // fix the path so that if running under the debugger we get the original
    // file
    string processName = Process.GetCurrentProcess().MainModule.FileName;
    int index = processName.IndexOf("vshost", StringComparison.Ordinal);
    if (index != -1)
    {
        string first = processName.Substring(0, index);
        int numChars = processName.Length - (index + 7);
        string second = processName.Substring(index + 7, numChars);

        processName = first + second;
    }
    return processName;
}

Note that this method does not account for assemblies loaded via Assembly. ReflectionOnlyLoad* methods, as it is inspecting for only compile-time references.

Warning

When loading assemblies for inspection using reflection, you should use the ReflectionOnlyLoad* methods. These methods do not allow you to execute code from the loaded assembly. The reasoning is that you may not know if you are loading assemblies containing hostile code or not. These methods prevent any hostile code from executing.

See Also

The “Assembly Class” topic in the MSDN documentation.

6.2 Determining Type Characteristics in Assemblies

Problem

You need to find types with certain characteristics in an assembly, such as:

  • By method name

  • Types available outside the assembly

  • Serializable types

  • Subclasses of a given type

  • Nested types

Solution

Use reflection to enumerate the types that match the characteristics you are looking for. For the characteristics we have outlined, you would use the methods listed in Table 6-1.

Table 6-1. Finding types by characteristics
Characteristic Reflection method
Method name Type.GetMember
Exported types Assembly.GetExportedTypes()
Serializable types Type.IsSerializeable
Subclasses of a type Type.IsSubclassOf
Nested types Type.GetNestedTypes

To find methods by name in an assembly, use the extension method GetMembersInAssembly:

public static IEnumerable<MemberInfo> GetMembersInAssembly(this Assembly asm,
    string memberName) =>
    from type in asm.GetTypes()
        from ms in type.GetMember(memberName, MemberTypes.All,
            BindingFlags.Public | BindingFlags.NonPublic |
            BindingFlags.Static | BindingFlags.Instance)
        select ms;

GetMembersInAssembly uses Type.GetMember to search for all members that have a matching name and returns the set of MethodInfos for those:

var members = asm.GetMembersInAssembly(memberSearchName);

For types available outside an assembly, use Assembly.GetExportedTypes to obtain the exported types of an assembly:

var types = asm.GetExportedTypes();

To determine the Serializable types in an assembly, use the extension method GetSerializableTypes:

public static IEnumerable<Type> GetSerializableTypes(this Assembly asm) =>
    from type in asm.GetTypes()
    where type.IsSerializable && 
        !type.IsNestedPrivate // filters out anonymous types
    select type;

GetSerializableType uses the Type.IsSerializable property to determine if the type supports serialization and returns a set of serializable types. Instead of testing the implemented interfaces and attributes on every type, you can query the Type.IsSerialized property to determine whether it is marked as serializable:

var serializeableTypes = asm.GetSerializableTypes();

To get the set of types in an assembly that subclass a particular type, use the extension method GetSubclassesForType:

public static IEnumerable<Type> GetSubclassesForType(this Assembly asm,
                                                        Type baseClassType) =>
    from type in asm.GetTypes()
    where type.IsSubclassOf(baseClassType)
    select type;

GetSubclassesForType uses the Type.IsSubclassOf method to determine which types in the assembly subclass the given type and accepts an assembly path string and a type to represent the base class. This method returns an IEnumerable<Type> representing the subclasses of the type passed to the baseClassType parameter. In the example, first you get the assembly path from the current process, and then you set up use of CSharpRecipes.ReflectionUtils+BaseOverrides as the type to test for subclasses. You call GetSubClassesForType, and it returns an IEnumerable<Type>:

Type type = Type.GetType(
    "CSharpRecipes.ReflectionAndDynamicProgramming+BaseOverrides");
var subClasses = asm.GetSubclassesForType(type);

Finally, to determine the nested types in an assembly, use the extension method GetNestedTypes:

public static IEnumerable<Type> GetNestedTypes(this Assembly asm) =>
    from t in asm.GetTypes()
        from t2 in t.GetNestedTypes(BindingFlags.Instance |
                    BindingFlags.Static |
                    BindingFlags.Public |
                    BindingFlags.NonPublic)
        where !t2.IsEnum && !t2.IsInterface &&
              !t2.IsNestedPrivate // filters out anonymous types
        select t2;

GetNestedTypes uses the Type.GetNestedTypes method and inspects each type in the assembly to determine if it has nested types:

var nestedTypes = asm.GetNestedTypes();

Discussion

Why should you care about these random facts about types in assemblies? Because they help you figure out how you are constructing your code and discover coding practices you may or may not want to allow. Let’s look at each one individually so you can see why you might want to know about it.

Method name

The memberName argument can contain the wildcard character * to indicate any character or characters. So, to find all methods starting with the string "Test", pass the string "Test*" to the memberName parameter. Note that the memberName argument is case-sensitive, but the asmPath argument is not. If you’d like to do a case-insensitive search for members, add the BindingFlags.IgnoreCase flag to the other BindingFlags in the call to Type.GetMember.

The GetMember method of the System.Type class is useful for finding one or more methods within a type. This method returns an array of MemberInfo objects that describe any members that match the given parameters.

Note

The * character may be used as a wildcard character only at the end of the name parameter string. If placed anywhere else in the string, it will not be treated as a wildcard character. In addition, it must be the only character in the name parameter to ensure that all members are returned. No other wildcard characters, such as ?, are supported.

Once you obtain an array of MemberInfo objects, you need to examine what kind of members they are. The MemberInfo class contains a MemberType property that returns a System.Reflection.MemberTypes enumeration value, which can be any of the values defined in Table 6-2 except All.

Table 6-2. MemberTypes enumeration values
Enumeration value Definition
All All member types
Constructor A constructor member
Custom A custom member type
Event An event member
Field A field member
Method A method member
NestedType A nested type
Property A property member
TypeInfo A type member that represents a TypeInfo member

Exported types

Obtaining the exported types in an assembly is useful when you are trying to determine the public interface to that assembly. This ability can greatly aid someone learning to use a new assembly or can aid the assembly developer in determining all the assembly’s access points to verify that they are adequately secure from malicious code. To get these exported types, use the GetExportedTypes method on the System.Reflection.Assembly type. The exported types consist of all of the types that are publicly accessible from outside of the assembly. A type may be publicly accessible but not be accessible from outside of the assembly. Take, for example, the following code:

public class Outer
{
    public class Inner {}
    private class SecretInner {}
}

The exported types are Outer and Outer.Inner; the type SecretInner is not exposed to the world outside of this assembly. If you change the Outer accessibility from public to private, you now have no types accessible to the outside world—the Inner class access level is downgraded because of the private on the Outer class.

Serializable types

A type may be marked as serializable with the SerializableAttribute attribute. Testing for SerializableAttribute on a type can turn into a fair amount of work. This is because SerializableAttribute is a magic attribute that the C# compiler actually strips off your code at compile time. Using ildasm (the .NET platform decompiler), you will see that this custom attribute just isn’t there—normally you see a .custom entry for each custom attribute, but not with SerializableAttribute. The C# compiler removes it and instead sets a flag in the metadata of the class. In source code, it looks like a custom attribute, but it compiles into one of a small set of attributes that gets a special representation in metadata. That’s why it gets special treatment in the reflection APIs. Fortunately, you do not have to do all of this work. The IsSerializable property on the Type class returns true if the current type is marked as serializable with the SerializableAttribute; otherwise, this property returns false.

Subclasses of a type

The IsSubclassOf method on the Type class allows you to determine whether the current type is a subclass of the type passed in to this method. Knowing if a type has been subclassed allows you to explore the type hierarchy that your team or company has created and can lead to opportunities for code reuse, refactoring, or developing a better understanding of the dependencies in the code base.

Nested types

Determining the nested types allows you to programmatically examine various aspects of some design patterns. Various design patterns may specify that a type will contain another type; for example, the Decorator and State design patterns make use of object containment.

The GetNestedTypes extension method uses a LINQ query to query all types in the assembly specified by the asmPath parameter. The LINQ query also queries for the nested types with the assembly by using the Type.GetNestedTypes method of the Type class.

Usually the dot operator is used to delimit namespaces and types; however, nested types are somewhat special. You set nested types apart from other types by using the + operator in their fully qualified name when dealing with them in the reflection APIs. By passing this fully qualified name in to the static GetType methods, you can acquire the actual type that it represents.

These methods return a Type object that represents the type identified by the typeName parameter.

Note

Calling Type.GetType to retrieve a type defined in a dynamic assembly (one that is created using the types defined in the System.Reflection.Emit namespace) returns a null if that assembly has not already been persisted to disk. Typically, you would use the static Assembly.GetType method on the dynamic assembly’s Assembly object.

See Also

The “Assembly Class,” “Type Class,” “TypeAttributes Enumeration,” “Determining and Obtaining Nested Types Within an Assembly,” “BindingFlags Enumeration,” “MemberInfo Class,” and “Finding Members in an Assembly” topics in the MSDN documentation.

6.3 Determining Inheritance Characteristics

Problem

You need to determine the inheritance characteristics of types such as:

  • The inheritance hierarchy

  • Base class methods that are overridden

Solution

Use reflection to enumerate the inheritance chains and base class method overrides, as shown in Table 6-3.

Table 6-3. Finding types by characteristics
Characteristic Reflection method
Inheritance hierarchy Type.BaseType
Base class methods MethodInfo.GetBaseDefinition

Use the extension method GetInheritanceChain to retrieve the entire inheritance hierarchy for a single type. GetInheritanceChain uses the GetBaseTypes method to enumerate the types and then reverses the default order to present the enumerated list sorted from base type to derived type. In other words, when GetBaseTypes traverses the BaseType property of each type it encounters, the resulting list of types is ordered from most derived to least derived, so we call Reverse to order the list with the least derived type (Object) first:

public static IEnumerable<Type> GetInheritanceChain(this Type derivedType) =>
    (from t in derivedType.GetBaseTypes()
    select t).Reverse();

private static IEnumerable<Type> GetBaseTypes(this Type type)
{
    Type current = type;
    while (current != null)
    {
        yield return current;
        current = current.BaseType;
    }
}

If you wanted to do this for all types in an assembly, you could use the extension method GetTypeHierarchies, which uses the custom TypeHierarchy class to represent the derived type and its inheritance chain:

public class TypeHierarchy
{
    public Type DerivedType { get; set; }
    public IEnumerable<Type> InheritanceChain { get; set; }
}

public static IEnumerable<TypeHierarchy> GetTypeHierarchies(this Assembly asm) =>
    from Type type in asm.GetTypes()
    select new TypeHierarchy
    {
        DerivedType = type,
        InheritanceChain = GetInheritanceChain(type)
    };

GetTypeHierarchies projects each type as the DerivedType and uses GetInheritanceChain to determine the InheritanceChain for the type.

To determine if base class methods are being overridden, use the MethodInfo.GetBaseDefinition method to determine which method is overridden in what base class. The extension method GetMethodOverrides shown in Example 6-1 examines all of the public instance methods in a class and displays which methods override their respective base class methods. This method also determines which base class the overridden method is in. This extension method is based on Type and uses the type to find overriding methods.

Example 6-2. The GetMethodOverrides methods
public class ReflectionUtils
{
    public static IEnumerable<MemberInfo> GetMethodOverrides(this Type type) =>
        from ms in type.GetMethods(BindingFlags.Instance |
                                BindingFlags.NonPublic | BindingFlags.Public |
                                BindingFlags.Static | BindingFlags.DeclaredOnly)
        where ms != ms.GetBaseDefinition()
        select ms.GetBaseDefinition;

The next extension method, GetBaseMethodOverridden, allows you to determine whether a particular method overrides a method in its base class and to get the MethodInfo for that overridden method back. It also extends Type, the full method name, and an array of Type objects representing its parameter types:

public class ReflectionUtils
{
    public static MethodInfo GetBaseMethodOverridden(this Type type,
                                            string methodName, Type[] paramTypes)
    {
        MethodInfo method = type.GetMethod(methodName, paramTypes);
        MethodInfo baseDef = method?.GetBaseDefinition();
        if (baseDef != method)
        {
            bool foundMatch = (from p in baseDef.GetParameters()
                        join op in paramTypes
                            on p.ParameterType.UnderlyingSystemType
                                equals op.UnderlyingSystemType
                        select p).Any();

            if (foundMatch)
                return baseDef;
        }
        return null;
    }
}

Discussion

Inheritance hierarchy

Unfortunately, no property of the Type class exists to obtain the inheritance hierarchy of a type. The DisplayInheritanceChain methods in this recipe, however, allow you to do so. All that is required is the assembly path and the name of the type with the inheritance hierarchy you wish to obtain. The DisplayInheritanceChain method requires only an assembly path since it displays the inheritance hierarchy for all types within that assembly.

The core code of this recipe exists in the GetBaseTypes method. This is a recursive method that walks each inherited type until it finds the ultimate base class—which is always the object class. Once it arrives at this ultimate base class, it returns to its caller. Each time the method returns to its caller, the next base class in the inheritance hierarchy is added to the list until the final GetBaseTypes method returns the completed inheritance chain.

To display the inheritance chain of a type, use the DisplayInheritanceChain method call.

private static void DisplayInheritanceChain(IEnumerable<Type> chain)
{
    StringBuilder builder = new StringBuilder();
    foreach (var type in chain)
    {
        if (builder.Length == 0)
            builder.Append(type.Name);
        else
            builder.AppendFormat($"<-{type.Name}");
    }
    Console.WriteLine($"Base Type List: {builder.ToString()}");
}

To display the inheritance hierarchy of all types in an assembly, use GetTypeHierarchies in conjunction with DisplayInheritanceChain:

// all types in the assembly
var typeHierarchies = asm.GetTypeHierarchies();
foreach (var th in typeHierarchies)
{
    // Recurse over all base types
    Console.WriteLine($"Derived Type: {th.DerivedType.FullName}");
    DisplayInheritanceChain(th.InheritanceChain);
    Console.WriteLine();
}

These methods result in output like the following:

Derived Type: CSharpRecipes.Reflection
Base Type List: Object<-Reflection
Derived Type: CSharpRecipes.ReflectionUtils+BaseOverrides
Base Type List: Object<-BaseOverrides

Derived Type: CSharpRecipes.ReflectionUtils+DerivedOverrides
Base Type List: Object<-BaseOverrides <-DerivedOverrides

This output shows that the base type list (or inheritance hierarchy) of the Reflection class in the CSharpRecipes namespace starts with Object (like all class and struct types in .NET). The nested class BaseOverrides also shows a base type list starting with Object. The nested class DerivedOverrides shows a more interesting base type list, where DerivedOverrides derives from BaseOverrides, which derives from Object.

Base class methods that are overridden

Determining which methods override their base class methods would be a tedious chore if it were not for the GetBaseDefinition method of the System.Reflection.MethodInfo type. This method takes no parameters and returns a MethodInfo object that corresponds to the overridden method in the base class. If this method is used on a MethodInfo object representing a method that is not being overridden—as is the case with a virtual or abstract method—GetBaseDefinition returns the original MethodInfo object.

The Type object’s GetMethod method is called when both the method name and its parameter array are passed in to GetBaseMethodOverridden; otherwise, GetMethods is used for GetMethodOverrides. If the method is correctly located and its MethodInfo object obtained, the GetBaseDefinition method is called on that MethodInfo object to get the first overridden method in the nearest base class in the inheritance hierarchy. This MethodInfo type is compared to the MethodInfo type on which the GetBaseDefinition method was called. If these two objects are the same, it means that there were no overridden methods in any base classes; therefore, nothing is returned. This code will return only the overridden method; if no methods are overridden, then null is returned.

The following code shows how to use each of these overloaded methods:

Type derivedType =
  asm.GetType("CSharpRecipes.ReflectionAndDynamicProgramming+DerivedOverrides",
    true, true);

var methodOverrides = derivedType.GetMethodOverrides();
foreach (MethodInfo mi in methodOverrides)
{
    Console.WriteLine();
    Console.WriteLine($"Current Method: {mi.ToString()}");
    Console.WriteLine($"Base Type FullName:  {mi.DeclaringType.FullName}");
    Console.WriteLine($"Base Method:  {mi.ToString()}");
    // list the types of this method
    foreach (ParameterInfo pi in mi.GetParameters())
    {
        Console.WriteLine($"	Param {pi.Name} : {pi.ParameterType.ToString()}");
    }
}

// try the signature findmethodoverrides
string methodName = "Foo";
var baseTypeMethodInfo = derivedType.GetBaseMethodOverridden(methodName,
    new Type[3] { typeof(long), typeof(double), typeof(byte[]) });
Console.WriteLine(
    $"{Environment.NewLine}For [Type] Method: [{derivedType.Name}]" +
    $" {methodName}");
Console.WriteLine(
    $"Base Type FullName: {baseTypeMethodInfo.ReflectedType.FullName}");
Console.WriteLine($"Base Method: {baseTypeMethodInfo}");
foreach (ParameterInfo pi in baseTypeMethodInfo.GetParameters())
{
    // list the params so we can see which one we got
    Console.WriteLine($"	Param {pi.Name} : {pi.ParameterType.ToString()}");
}

In the usage code, you get the path to the test code assembly (CSharpRecipes.exe) via the Process class. You then use that to find a class that has been defined in the ReflectionUtils class, called DerivedOverrides, which derives from BaseOverrides. DerivedOverrides and BaseOverrides are both shown here:

public abstract class BaseOverrides
{
    public abstract void Foo(string str, int i);

    public abstract void Foo(long l, double d, byte[] bytes);
}

public class DerivedOverrides : BaseOverrides
{
    public override void Foo(string str, int i)
    {
    }

    public override void Foo(long l, double d, byte[] bytes)
    {
    }
}

GetMethodOverrides returns every overridden method for each method it finds in the Reflection.DerivedOverrides type. If you want to display all overriding methods and their corresponding overridden methods, you can remove the BindingFlags.DeclaredOnly binding enumeration from the GetMethods method call:

return from ms in type.GetMethods(BindingFlags.Instance |
           BindingFlags.NonPublic | BindingFlags.Public)
       where ms != ms.GetBaseDefinition()
       select ms.GetBaseDefinition();

GetBaseMethodOverridden passes a method name, and the parameters for this method, to find the override that specifically matches the signature based on the parameters. In this case, the parameter types of method Foo are long, double, and byte[]. This method displays the method that DerivedOverrides.Foo overrides.

See Also

The “Assembly Class,” “Type.BaseType Method,” “Finding Members in an Assembly,” “MethodInfo Class,” and “ParameterInfo Class” topics in the MSDN documentation.

6.4 Invoking Members Using Reflection

Problem

You have a list of method names that you wish to invoke dynamically within your application. As your code executes, it will pull names off this list and attempt to invoke these methods. This technique might be useful to create a test harness for components that reads in the methods to execute from an XML (or JSON) file and executes them with the given arguments.

Solution

The TestReflectionInvocation method shown in Example 6-3 calls the ReflectionInvoke method, which opens the XML configuration file, reads out the test information using LINQ, and executes each test method.

Example 6-3. Invoking members via reflection
public static void TestReflectionInvocation()
{
    XDocument xdoc =
        XDocument.Load(@"....SampleClassLibrarySampleClassLibraryTests.xml");
    ReflectionInvoke(xdoc, @"SampleClassLibrary.dll");
}

This is the XML document in which the test method information is contained:

<?xml version="1.0" encoding="utf-8" ?>
<Tests>
    <Test className='SampleClassLibrary.SampleClass'
    methodName='TestMethod1'>
        <Argument>Running TestMethod1</Argument>
    </Test>
    <Test className='SampleClassLibrary.SampleClass'
    methodName='TestMethod2'>
        <Parameter>Running TestMethod2</Parameter>
        <Parameter>27</Parameter>
    </Test>
</Tests>

ReflectionInvoke, as shown in Example 6-4, dynamically invokes the method that is passed to it using the information contained in the XDocument. This code determines each parameter’s type by examining the ParameterInfo items on the MethodInfo, and then converts the values to the actual type from a string via the Convert.ChangeType method. Finally, the return value of the invoked method is returned by the MethodBase.Invoke method.

Example 6-4. ReflectionInvoke method
public static void ReflectionInvoke(XDocument xdoc, string asmPath)
{
    var test = from t in xdoc.Root.Elements("Test")
                select new
                {
                    typeName = (string)t.Attribute("className").Value,
                    methodName = (string)t.Attribute("methodName").Value,
                    parameter = from p in t.Elements("Parameter")
                                select new { arg = p.Value }
                };

    // Load the assembly
    Assembly asm = Assembly.LoadFrom(asmPath);

    foreach (var elem in test)
    {
        // create the actual type
        Type reflClassType = asm.GetType(elem.typeName, true, false);

        // Create an instance of this type and verify that it exists
        object reflObj = Activator.CreateInstance(reflClassType);
        if (reflObj != null)
        {
            // Verify that the method exists and get its MethodInfo obj
            MethodInfo invokedMethod = reflClassType.GetMethod(elem.methodName);
            if (invokedMethod != null)
            {
                // Create the argument list for the dynamically invoked methods
                object[] arguments = new object[elem.parameter.Count()];
                int index = 0;

                // for each parameter, add it to the list
                foreach (var arg in elem.parameter)
                {
                    // get the type of the parameter
                    Type paramType =
                        invokedMethod.GetParameters()[index].ParameterType;

                    // change the value to that type and assign it
                    arguments[index] =
                        Convert.ChangeType(arg.arg, paramType);
                    index++;
                }

                // Invoke the method with the parameters
                object retObj = invokedMethod.Invoke(reflObj, arguments);

                Console.WriteLine($"	Returned object: {retObj}");
                Console.WriteLine($"	Returned object: {retObj.GetType().FullName}");
            }
        }
    }
}

These are the dynamically invoked methods located on the SampleClass type in the SampleClassLibrary assembly:

public bool TestMethod1(string text)
{
    Console.WriteLine(text);
    return (true);
}

public bool TestMethod2(string text, int n)
{
    Console.WriteLine(text + " invoked with {0}",n);
    return (true);
}

And here is the output from these methods:

Running TestMethod1
        Returned object: True
        Returned object: System.Boolean
Running TestMethod2 invoked with 27
        Returned object: True
        Returned object: System.Boolean

Discussion

Reflection enables you to dynamically invoke both static and instance methods within a type in either the same assembly or in a different one. This can be a very powerful tool to allow your code to determine at runtime which method to call. This determination can be based on an assembly name, a type name, or a method name, though the assembly name is not required if the method exists in the same assembly as the invoking code, if you already have the Assembly object, or if you have a Type object for the class the method is on.

Note

As always, with great power comes great responsibility. Dynamically loading an assembly without knowing the origin (or even invoking a legit one in an elevated context) can cause unwanted consequences, so use this technique wisely and securely!

This technique may seem similar to delegates since both can dynamically determine at runtime which method is to be called. Delegates, on the whole, require you to know signatures of methods you might call at runtime, whereas with reflection, you can invoke methods when you have no idea of the signature, providing a much looser binding. However, you will still have to pass in reasonable arguments. More dynamic invocation can be achieved with Delegate.DynamicInvoke, but this is more of a reflection-based method than the traditional delegate invocation.

The DynamicInvoke method shown in the Solution contains all the code required to dynamically invoke a method. This code first loads the assembly using its assembly name (passed in through the asmPath parameter). Next, it gets the Type object for the class containing the method to invoke (it obtains the class name from the Test element’s className attribute using LINQ). It then retrieves the method name from the Test element’s methodName attribute using LINQ. Once you have all of the information from the Test element, an instance of the Type object is created, and you then invoke the specified method on this created instance:

  • First, the static Activator.CreateInstance method is called to actually create an instance of the Type object contained in the local variable dynClassType. The method returns an object reference to the instance of type that was created or throws an exception if the object cannot be created.

  • Once you have successfully obtained the instance of this class, the MethodInfo object of the method to be invoked is acquired through a call to GetMethod on the Type object.

The instance of the object created with the CreateInstance method is then passed as the first parameter to the MethodInfo.Invoke method. This method returns an object containing the return value of the invoked method. This object is then returned by InvokeMethod. The second parameter to MethodInfo.Invoke is an object array containing any parameters to be passed to this method. This array is constructed based on the number of Parameter elements under each Test element in the XML. You then look at the ParameterInfo of each parameter (obtained from MethodInfo. GetParameters) and use the Convert.ChangeType method to coerce the string value from the XML to the proper type.

The DynamicInvoke method finally displays each returned object value and its type. Note that there is no extra logic required to return different return values from the invoked methods since they are all returned as an object, unlike when you pass differing arguments to the invoked methods.

See Also

The “Activator Class,” “MethodInfo Class,” “Convert.ChangeType Method,” and “ParameterInfo Class” topics in the MSDN documentation.

6.5 Accessing Local Variable Information

Problem

You are building a tool that examines code, and you need to get access to the local variables within a method.

Solution

Use the LocalVariables property on the MethodBody class to return an IList of LocalVariableInfo objects, each of which describes a local variable within the method:

public static ReadOnlyCollection<LocalVariableInfo>
GetLocalVars(string asmPath, string typeName, string methodName)
{
    Assembly asm = Assembly.LoadFrom(asmPath);
    Type asmType = asm.GetType(typeName);
    MethodInfo mi = asmType.GetMethod(methodName);
    MethodBody mb = mi.GetMethodBody();

    System.Collections.ObjectModel.ReadOnlyCollection<LocalVariableInfo> vars =
        (System.Collections.ObjectModel.ReadOnlyCollection<LocalVariableInfo>)
            mb.LocalVariables;

    // Display information about each local variable
    foreach (LocalVariableInfo lvi in vars)
    {
        Console.WriteLine($"IsPinned: {lvi.IsPinned}");
        Console.WriteLine($"LocalIndex: {lvi.LocalIndex}");
        Console.WriteLine($"LocalType.Module: {lvi.LocalType.Module}");
        Console.WriteLine($"LocalType.FullName: {lvi.LocalType.FullName}");
        Console.WriteLine($"ToString(): {lvi.ToString()}");
    }

    return (vars);
}

You can call the GetLocalVars method using the following code:

public static void TestGetLocalVars()
{
    string file = GetProcessPath();

    // Get all local var info for the 
    // CSharpRecipes.Reflection.GetLocalVars method
    System.Collections.ObjectModel.ReadOnlyCollection<LocalVariableInfo> vars =
        GetLocalVars(file, "CSharpRecipes.ReflectionAndDynamicProgramming",
            "GetLocalVars");
}

GetProcessPath, shown here, returns the current path to the process executable:

private static string GetProcessPath()
{
    // fix the path so that if running under the debugger we get the 
    // original file
    string processName = Process.GetCurrentProcess().MainModule.FileName;
    int index = processName.IndexOf("vshost", StringComparison.Ordinal);
    if (index != -1)
    {
        string first = processName.Substring(0, index);
        int numChars = processName.Length - (index + 7);
        string second = processName.Substring(index + 7, numChars);

        processName = first + second;
    }
    return processName;
}

Here is the output of this method:

IsPinned: False
LocalIndex: 0
LocalType.Module: CommonLanguageRuntimeLibrary
LocalType.FullName: System.Reflection.Assembly
ToString(): System.Reflection.Assembly (0)
IsPinned: False
LocalIndex: 1
LocalType.Module: CommonLanguageRuntimeLibrary
LocalType.FullName: System.Type
ToString(): System.Type (1)
IsPinned: False
LocalIndex: 2
LocalType.Module: CommonLanguageRuntimeLibrary
LocalType.FullName: System.Reflection.MethodInfo
ToString(): System.Reflection.MethodInfo (2)
IsPinned: False
LocalIndex: 3
LocalType.Module: CommonLanguageRuntimeLibrary
LocalType.FullName: System.Reflection.MethodBody
ToString(): System.Reflection.MethodBody (3)
IsPinned: False
LocalIndex: 4
LocalType.Module: CommonLanguageRuntimeLibrary
LocalType.FullName: System.Collections.ObjectModel.ReadOnlyCollection`1[[System.
Reflection.LocalVariableInfo, mscorlib, Version=4.0.0.0, Culture=neutral, Public
KeyToken=b77a5c561934e089]]
ToString(): System.Collections.ObjectModel.ReadOnlyCollection`1[System.Reflectio
n.LocalVariableInfo] (4)
IsPinned: False
LocalIndex: 5
LocalType.Module: CommonLanguageRuntimeLibrary
LocalType.FullName: System.Collections.Generic.IEnumerator`1[[System.Reflection.
LocalVariableInfo, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b7
7a5c561934e089]]
ToString(): System.Collections.Generic.IEnumerator`1[System.Reflection.LocalVari
ableInfo] (5)
IsPinned: False
LocalIndex: 6
LocalType.Module: CommonLanguageRuntimeLibrary
LocalType.FullName: System.Reflection.LocalVariableInfo
ToString(): System.Reflection.LocalVariableInfo (6)
IsPinned: False
LocalIndex: 7
LocalType.Module: CommonLanguageRuntimeLibrary
LocalType.FullName: System.Collections.ObjectModel.ReadOnlyCollection`1[[System.
Reflection.LocalVariableInfo, mscorlib, Version=4.0.0.0, Culture=neutral, Public
KeyToken=b77a5c561934e089]]
ToString(): System.Collections.ObjectModel.ReadOnlyCollection`1[System.Reflectio
n.LocalVariableInfo] (7)

The LocalVariableInfo objects for each local variable found in the CSharpRecipes.Reflection.GetLocalVars method will be returned in the vars IList collection.

Discussion

The LocalVariables property can give you a good amount of information about variables within a method. It returns an IList<LocalVariableInfo> collection. Each LocalVariableInfo object contains the information described in Table 6-4.

Table 6-4. LocalVariableInfo information
Member Definition
IsPinned Returns a bool indicating if the object that this variable refers to is pinned in memory (true) or not (false). In unmanaged code, an object must be pinned before it can be referred to by an unmanaged pointer. While it is pinned, it cannot be moved by garbage collection.
LocalIndex Returns the index of this variable within this method’s body.
LocalType Returns a Type object that describes the type of this variable.
ToString Returns the LocalType.FullName, a space, and then the LocalIndex value surrounded by parentheses.

See Also

The “MethodInfo Class,” “MethodBody Class,” “ReadOnlyCollection<T> Class,” and “LocalVariableInfo Class” topics in the MSDN documentation.

6.6 Creating a Generic Type

Problem

You want to create a generic type using only the reflection APIs.

Solution

You create a generic type similarly to how you create a nongeneric type; however, there is an extra step to create the type arguments you want to use and to bind these type arguments to the generic type’s type parameters at construction. You will use a new method added to the Type class called BindGenericParameters:

public static void CreateDictionary()
{
    // Get the type we want to construct
    Type typeToConstruct = typeof(Dictionary<,>);
    // Get the type arguments we want to construct our type with
    Type[] typeArguments = {typeof(int), typeof(string)};
    // Bind these type arguments to our generic type
    Type newType = typeToConstruct.MakeGenericType(typeArguments);

    // Construct our type
    Dictionary<int, string> dict =
        (Dictionary<int, string>)Activator.CreateInstance(newType);

    // Test our newly constructed type
    Console.WriteLine($"Count == {dict.Count}");
    dict.Add(1, "test1");
    Console.WriteLine($"Count == {dict.Count}");
}

This is the code to test the CreateDictionary method:

public static void TestCreateMultiMap()
{
    Assembly asm = Assembly.LoadFrom("C:\CSCB6 " +
             "\Code\CSharpRecipes\bin\Debug\CSharpRecipes.exe");
    CreateDictionary(asm);
}

And here is the output of this method:

Count == 0
Count == 1

Discussion

Type parameters are defined on a class and indicate that any type that can be converted to an Object can be substituted for this type parameter (unless, of course, there are constraints placed on this type parameter via the where keyword). For example, the following class has two type parameters, T and U:

public class Foo<T, U> {...}
Note

Of course, you do not have to use T and U; you can instead use another letter or even a full name, such as TypeParam1 and TypeParam2.

A type argument is defined as the actual type that will be substituted for the type parameter. In the previously defined class Foo, you can replace type parameter T with the type argument int, and type parameter U with the type argument string.

The BindGenericParameters method allows you to substitute type parameters with actual type arguments. This method accepts a single Type array parameter. This Type array consists of each type argument that will be substituted for each type parameter of the generic type. These type arguments must be added to this Type array in the same order as they are defined on the class. For example, the Foo class defines type parameters T and U, in that order. The Type array that you define contains an int type and a string type, in that order. This means that the type parameter T will be substituted for the type argument int, and U will be replaced with a string type. The BindGenericParameters method returns a Type object of the type you specified along with the type arguments.

See Also

The “Type.BindGenericParameters method” topic in the MSDN documentation.

6.7 Using dynamic Versus object

Problem

You want to know the differences between using dynamic and object as the type specification.

Solution

To demonstrate the primary difference between dynamic and object, we will revisit the sample class we used in Recipe 6.4. That code dynamically loaded an instance of the SampleClass type and then, using an XML file and reflection, ran certain operations on the instance. That instance was of type object. If we created the type and made it dynamic, we could actually write the code to call the methods right in the code (giving up the flexibility of the first example but making the code much neater) even though our dynamic object instance is not of type SampleClass:

// Load the assembly
Assembly asm = Assembly.LoadFrom(@"SampleClassLibrary.dll");

// Get the SampleClass type
Type reflClassType = asm?.GetType("SampleClassLibrary.SampleClass", true, false);

if (reflClassType != null)
{
    // Create our sample class instance
    dynamic sampleClass = Activator.CreateInstance(reflClassType);
    Console.WriteLine($"LastMessage: {sampleClass.LastMessage}");
    Console.WriteLine("Calling TestMethod1");
    sampleClass.TestMethod1("Running TestMethod1");
    Console.WriteLine($"LastMessage: {sampleClass.LastMessage}");
    Console.WriteLine("Calling TestMethod2");
    sampleClass.TestMethod2("Running TestMethod2", 27);
    Console.WriteLine($"LastMessage: {sampleClass.LastMessage}");
}

Notice that we can call the methods directly without error even though the type of the object instance is dynamic. This is because the compiler knows to defer type checking of these calls (LastMessage, TestMethod1, TestMethod2) until runtime. Although dynamic is treated like object and even ultimately compiles to object, it tells the compiler, “Hey relax, I know what I’m doing!” and allows you to invoke methods and properties that the compiler can’t resolve.

The output of this example is shown here:

LastMessage: Not set yet
Calling TestMethod1
Running TestMethod1
LastMessage: Running TestMethod1
Calling TestMethod2
Running TestMethod2 invoked with 27
LastMessage: Running TestMethod2

Discussion

The dynamic type allows you to bypass compile-time type checking and binds the operations to call sites at runtime.

Warning

Just remember, if you aren’t finding out until runtime, you might see exceptions you weren’t expecting if you access things on a dynamic object that are not present.

Most of the time, dynamic acts just like object, with the main difference being the deferred checking. Once the operation for a dynamic type is invoked, the results of the binding are cached to help with performance the next time the operation is called. If you look at the IL for a dynamic method, you will see that the sampleClass local variable actually compiles down to the type object:

.locals init ([0] class [mscorlib]System.Reflection.Assembly asm,
         [1] class [mscorlib]System.Type reflClassType,
         [2] bool V_2,
         [3] object sampleClass)

If we tried to do the same operations on our SampleClass instance using object instead of dynamic, like this:

object objSampleClass = Activator.CreateInstance(reflClassType);
Console.WriteLine($"LastMessage: {objSampleClass.LastMessage}");
Console.WriteLine("Calling TestMethod1");
objSampleClass.TestMethod1("Running TestMethod1");
Console.WriteLine($"LastMessage: {objSampleClass.LastMessage}");
Console.WriteLine("Calling TestMethod2");
objSampleClass.TestMethod2("Running TestMethod2", 27);
Console.WriteLine($"LastMessage: {objSampleClass.LastMessage}");

We would get the following compiler errors:

Error CS1061  'object' does not contain a definition for 'LastMessage' and no
extension method 'LastMessage' accepting a first argument of type 'object' could
be found(are you missing a using directive or an assembly reference ?)
06_ReflectionAndDynamicProgramming.cs  482

Error CS1061  'object' does not contain a definition for 'TestMethod1' and no
extension method 'TestMethod1' accepting a first argument of type 'object' could
be found(are you missing a using directive or an assembly reference ?)
06_ReflectionAndDynamicProgramming.cs  484

Error CS1061  'object' does not contain a definition for 'LastMessage' and no
extension method 'LastMessage' accepting a first argument of type 'object' could
be found(are you missing a using directive or an assembly reference ?)
06_ReflectionAndDynamicProgramming.cs  485

Error CS1061  'object' does not contain a definition for 'TestMethod2' and no
extension method 'TestMethod2' accepting a first argument of type 'object' could
be found(are you missing a using directive or an assembly reference ?)
06_ReflectionAndDynamicProgramming.cs  487

Error CS1061  'object' does not contain a definition for 'LastMessage' and no
extension method 'LastMessage' accepting a first argument of type 'object' could
be found(are you missing a using directive or an assembly reference ?)
06_ReflectionAndDynamicProgramming.cs  488

See Also

The “dynamic” topic in the MSDN documentation.

6.8 Building Objects Dynamically

Problem

You want to be able to build up an object to work with on the fly at runtime.

Solution

Use ExpandoObject to create an object that you can add properties, methods, and events to and be able to bind data to in a user interface.

We can use ExpandoObject to create an initial object to hold someone’s Name and current Country:

dynamic expando = new ExpandoObject();
expando.Name = "Brian";
expando.Country = "USA";

Once we have added properties directly, we can also add properties to our object in a more dynamic fashion using the AddProperty method we have provided for you. One example of why you might do this is to add properties to your object from another source of data. We will add the Language property:

// Add properties dynamically to expando
AddProperty(expando, "Language", "English");

The AddProperty method takes advantage of ExpandoObject’s support for IDictionary<string, object> and allows us to add properties using values we determine at runtime:

public static void AddProperty(ExpandoObject expando, string propertyName,
    object propertyValue)
{
    // ExpandoObject supports IDictionary so we can extend it like this
    var expandoDict = expando as IDictionary<string, object>;
    if (expandoDict.ContainsKey(propertyName))
        expandoDict[propertyName] = propertyValue;
    else
        expandoDict.Add(propertyName, propertyValue);
}

We can also add methods to the ExpandoObject by using the Func<> generic type, which represents a method call. In our example, we will add a validation method for our object:

// Add method to expando
expando.IsValid = (Func<bool>)(() =>
{
    // Check that they supplied a name
    if(string.IsNullOrWhiteSpace(expando.Name))
        return false;
    return true;
});

if(!expando.IsValid())
{
    // Don't allow continuation...
}

Now we can also define and add events to the ExpandoObject using the Action<> generic type. We will add two events, LanguageChanged and CountryChanged. We’ll add LanguageChanged after defining the eventHandler variable to hold the Action<object,EventArgs>, and we’ll add CountryChanged directly as an inline anonymous method. CountryChanged looks at the Country that changed and invokes the LanguageChanged event with the proper Language for the Country. (Note that LanguageChanged is also an anonymous method, but sometimes it can make for cleaner code to have a variable for these.)

// You can also add event handlers to expando objects
var eventHandler =
    new Action<object, EventArgs>((sender, eventArgs) =>
    {
        dynamic exp = sender as ExpandoObject;
        var langArgs = eventArgs as LanguageChangedEventArgs;
        Console.WriteLine($"Setting Language to : {langArgs?.Language}");
        exp.Language = langArgs?.Language;
    });

// Add a LanguageChanged event and predefined event handler
AddEvent(expando, "LanguageChanged", eventHandler);

// Add a CountryChanged event and an inline event handler
AddEvent(expando, "CountryChanged",
    new Action<object, EventArgs>((sender, eventArgs) =>
{
    dynamic exp = sender as ExpandoObject;
    var ctryArgs = eventArgs as CountryChangedEventArgs;
    string newLanguage = string.Empty;
    switch (ctryArgs?.Country)
    {
        case "France":
            newLanguage = "French";
            break;
        case "China":
            newLanguage = "Mandarin";
            break;
        case "Spain":
            newLanguage = "Spanish";
            break;
    }
    Console.WriteLine($"Country changed to {ctryArgs?.Country}, " +
        $"changing Language to {newLanguage}");
    exp?.LanguageChanged(sender,
        new LanguageChangedEventArgs() { Language = newLanguage });
}));

We have provided the AddEvent method for you to encapsulate the details of adding the event to the ExpandoObject. This again takes advantage of ExpandoObject’s support of IDictionary<string,object>:

public static void AddEvent(ExpandoObject expando, string eventName,
Action<object, EventArgs> handler)
{
    var expandoDict = expando as IDictionary<string, object>;
    if (expandoDict.ContainsKey(eventName))
        expandoDict[eventName] = handler;
    else
        expandoDict.Add(eventName, handler);
}

Finally, ExpandoObject supports INotifyPropertyChanged, which is the foundation of binding data to properties in .NET. We hook up the event handler, and when the Country property is changed we fire the CountryChanged event:

 ((INotifyPropertyChanged)expando).PropertyChanged +=
    new PropertyChangedEventHandler((sender, ea) =>
{
    dynamic exp = sender as dynamic;
    var pcea = ea as PropertyChangedEventArgs;
    if(pcea?.PropertyName == "Country")
        exp.CountryChanged(exp, new CountryChangedEventArgs()
            { Country = exp.Country });
});

Now that we’ve finished constructing our object, we can invoke it like this to simulate our friend travelling around the world:

Console.WriteLine($"expando contains: {expando.Name}, {expando.Country}, " +
    $"{expando.Language}");
Console.WriteLine();

Console.WriteLine("Changing Country to France...");
expando.Country = "France";
Console.WriteLine($"expando contains: {expando.Name}, {expando.Country},  " +
    $"{expando.Language}");
Console.WriteLine();

Console.WriteLine("Changing Country to China...");
expando.Country = "China";
Console.WriteLine($"expando contains: {expando.Name}, {expando.Country},  " +
    $"{expando.Language}");
Console.WriteLine();

Console.WriteLine("Changing Country to Spain...");
expando.Country = "Spain";
Console.WriteLine($"expando contains: {expando.Name}, {expando.Country},  " +
    $"{expando.Language}");
Console.WriteLine();

The output of this example is shown here:

expando contains: Brian, USA, English

Changing Country to France...
Country changed to France, changing Language to French
Setting Language to: French
expando contains: Brian, France, French

Changing Country to China...
Country changed to China, changing Language to Mandarin
Setting Language to: Mandarin
expando contains: Brian, China, Mandarin

Changing Country to Spain...
Country changed to Spain, changing Language to Spanish
Setting Language to: Spanish
expando contains: Brian, Spain, Spanish

Discussion

ExpandoObject allows you to write code that is more readable than typical reflection code with GetProperty("Field") syntax. When you’re dealing with XML or JSON, ExpandoObject can be useful for quickly setting up a type to program against instead of always having to create data transfer objects. ExpandoObject’s support for data binding through INotifyPropertyChanged is a huge win for anyone using WPF, MVC, or any other binding framework in .NET, as it allows you to use these objects, as well as other statically typed classes, “on the fly.”

Since ExpandoObject can take delegates as members, you can attach methods and events to these dynamic types while the code looks like you are addressing a static type:

public static void AddEvent(ExpandoObject expando, string eventName,
Action<object, EventArgs> handler)
{
    var expandoDict = expando as IDictionary<string, object>;
    if (expandoDict.ContainsKey(eventName))
        expandoDict[eventName] = handler;
    else
        expandoDict.Add(eventName, handler);
}

You might be wondering why we didn’t use extension methods for AddProperty and AddEvent. They both could hang off of ExpandoObject and make the syntax even cleaner, right? Unfortunately, no. The way extension methods work is that the compiler does a search on all classes that might be a match for the extended class. This means that the DLR would have to know all of this information at runtime as well (since ExpandoObject is handled by the DLR), and currently not all of that information is encoded into the call site for the class and methods.

The event argument classes for the LanguageChanged and CountryChanged events are listed here:

public class LanguageChangedEventArgs : EventArgs
{
    public string Language { get; set; }
}

public class CountryChangedEventArgs : EventArgs
{
    public string Country { get; set; }
}

See Also

The “ExpandoObject class,” ”Func<> delegate,” “Action<> delegate,” and “INotifyPropertyChanged interface” topics in the MSDN documentation.

6.9 Make Your Objects Extensible

Problem

You want to have a base class for objects that will allow you to extend the objects at runtime so that you can derive your models from it and avoid duplicated code.

Solution

Use the DynamicBase<T> class derived from DynamicObject to create a new class or encapsulate an existing class:

public class DynamicBase<T> : DynamicObject
    where T : new()
{
    private T _containedObject = default(T);

    [JsonExtensionData] //JSON.NET 5.0 and above
    private Dictionary<string, object> _dynamicMembers =
        new Dictionary<string, object>();

    private List<PropertyInfo> _propertyInfos =
        new List<PropertyInfo>(typeof(T).GetProperties());

    public DynamicBase()
    {
    }
    public DynamicBase(T containedObject)
    {
        _containedObject = containedObject;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, 
        object[] args, out object result)
    {
        if (_dynamicMembers.ContainsKey(binder.Name) 
        && _dynamicMembers[binder.Name] is Delegate)
        {
            result = (_dynamicMembers[binder.Name] as Delegate).DynamicInvoke(
                args);
            return true;
        }

        return base.TryInvokeMember(binder, args, out result);
    }

    public override IEnumerable<string> GetDynamicMemberNames() => 
        _dynamicMembers.Keys;

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = null;
        var propertyInfo = _propertyInfos.Where(pi =>
            pi.Name == binder.Name).FirstOrDefault();
        // Make sure this member isn't a property on the object yet
        if (propertyInfo == null)
        {
            // look in the additional items collection for it
            if (_dynamicMembers.Keys.Contains(binder.Name))
            {
                // return the dynamic item
                result = _dynamicMembers[binder.Name];
                return true;
            }
        }
        else
        {
            // get it from the contained object
            if (_containedObject != null)
            {
                result = propertyInfo.GetValue(_containedObject);
                return true;
            }
        }
        return base.TryGetMember(binder, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        var propertyInfo = _propertyInfos.Where(pi =>
            pi.Name == binder.Name).FirstOrDefault();
        // Make sure this member isn't a property on the object yet
        if (propertyInfo == null)
        {
            // look in the additional items collection for it
            if (_dynamicMembers.Keys.Contains(binder.Name))
            {
                // set the dynamic item
                _dynamicMembers[binder.Name] = value;
                return true;
            }
            else
            {
                _dynamicMembers.Add(binder.Name, value);
                return true;
            }
        }
        else
        {
            // put it in the contained object
            if (_containedObject != null)
            {
                propertyInfo.SetValue(_containedObject, value);
                return true;
            }
        }
        return base.TrySetMember(binder, value);
    }

    public override string ToString()
    {
        StringBuilder builder = new StringBuilder();
        foreach (var propInfo in _propertyInfos)
        {
            if(_containedObject != null)
                builder.AppendFormat("{0}:{1}{2}", propInfo.Name,
                   propInfo.GetValue(_containedObject), Environment.NewLine);
            else
                builder.AppendFormat("{0}:{1}{2}", propInfo.Name,
                   propInfo.GetValue(this), Environment.NewLine);
        }
        foreach (var addlItem in _dynamicMembers)
        {
            // exclude methods that are added from the description
            Type itemType = addlItem.Value.GetType();
            Type genericType =
                itemType.IsGenericType ? 
                    itemType.GetGenericTypeDefinition() : null;
            if (genericType != null)
            {
                if (genericType != typeof(Func<>) &&
                    genericType != typeof(Action<>))
                    builder.AppendFormat("{0}:{1}{2}", addlItem.Key,
                        addlItem.Value, Environment.NewLine);
            }
            else
                builder.AppendFormat("{0}:{1}{2}", addlItem.Key, addlItem.Value,
                   Environment.NewLine);
        }
        return builder.ToString();
    }
}

To understand how DynamicBase<T> is used, consider a scenario where we have a web service that is receiving a serialized JSON payload of athlete information. Currently we have defined the DynamicAthlete class with properties for both a Name and a Sport:

public class DynamicAthlete : DynamicBase<DynamicAthlete>
{
    public string Name { get; set; }
    public string Sport { get; set; }
}

In the payload being sent to us, the supplier has started to send additional information about the Position the athlete plays. This can happen at times when legacy system integrations change and all systems cannot update at the same time. For our receiving system, we don’t want to lose the new data being sent from some systems. We simulate the construction of the JSON payload using dynamic and the JSON.NET serializer available via NuGet (thank you, James Newton-King—this thing rocks!):

// Create a set of information on athletes
// Note that the service receiving these doesn't have Position as a
// property on the Athlete object
dynamic initialAthletes = new[]
{
    new
    {
        Name = "Tom Brady",
        Sport = "Football",
        Position = "Quarterback"
    },
    new
    {
        Name = "Derek Jeter",
        Sport = "Baseball",
        Position = "Shortstop"
    },
    new
    {
        Name = "Michael Jordan",
        Sport = "Basketball",
        Position = "Small Forward"
    },
    new
    {
        Name = "Lionel Messi",
        Sport = "Soccer",
        Position = "Forward"
    }
};

// serialize the JSON to send to a web service about athletes...
string serializedAthletes = JsonNetSerialize(initialAthletes);

Assume the JSON payload for the athletes comes in to your service and is deserialized (once again, props to JSON.NET) and we deserialize it as an array of DynamicAthletes:

// deserialize the JSON we were sent
var athletes = JsonNetDeserialize<DynamicAthlete[]>(serializedAthletes);

Now, everyone who has done any kind of web service development (or any serialization development, for that matter) knows that if you don’t have a place to put things while deserializing, they get lost or cause errors. So what happens to the Position property value that was passed in since that property is not declared on DynamicAthlete? If you look back at the declaration of DynamicBase<T> (from which DynamicAthlete derives), you will see an internal private Dictionary<string,object> that is marked with the JsonExtensionData attribute. This attribute tells the serializer where to put property values that do not have a place in the derived object. How cool is that?! So our Position value is stored in this internal dictionary, which is great, but how do we access it?

[JsonExtensionData] //JSON.NET 5.0 and above
private Dictionary<string, object> _dynamicMembers =
    new Dictionary<string, object>();

Since our DynamicAthlete is derived from DynamicBase<T>, which in turn derives from DynamicObject, we can assign the first athlete we received into the dynamic variable da. Once it is in a dynamic variable, we can access Position just as if it were one of the defined properties of DynamicAthlete:

dynamic da = athletes[0];
Console.WriteLine($"Position of first athlete: {da.Position}");

So we can preserve the value of the properties sent to us even if we don’t know about them directly when we deploy the service, which is a nice robustness feature. We could also add a new method to each DynamicAthlete to get the Name in uppercase while printing out the contents we received:

// Inspect the athletes and see that we not only got the Position
// information, but we can also add an operation to work on the
// entity and invoke that as part of the dynamic entity
foreach(var athlete in athletes)
{
    dynamic dynamicAthlete = (dynamic)athlete;
    dynamicAthlete.GetUppercaseName =
        (Func<string>)(() =>
        {
            return ((string)dynamicAthlete.Name).ToUpper();
        });
    Console.WriteLine($"Athlete:");
    Console.WriteLine(athlete);
    Console.WriteLine($"Uppercase Name: {dynamicAthlete.GetUppercaseName()}");
    Console.WriteLine();
    Console.WriteLine();
}

GetUppercaseName is added to the object and then called to return the uppercase version of the Name. Here is the output:

Athlete:
Name:Tom Brady
Sport:Football
Position:Quarterback

Uppercase Name: TOM BRADY


Athlete:
Name:Derek Jeter
Sport:Baseball
Position:Shortstop

Uppercase Name: DEREK JETER


Athlete:
Name:Michael Jordan
Sport:Basketball
Position:Small Forward

Uppercase Name: MICHAEL JORDAN


Athlete:
Name:Lionel Messi
Sport:Soccer
Position:Forward

Uppercase Name: LIONEL MESSI

What about the case where we already have our objects defined? How can we get in on this extension goodness? Let’s look at the StaticAthlete class as an example:

public class StaticAthlete
{
    public string Name { get; set; }
    public string Sport { get; set; }
}

StaticAthlete looks almost the same as DynamicAthlete, but it is not derived from anything.

If we create an instance of StaticAthlete, we can still use DynamicBase<T> to wrap it and get the same extension behavior as we did when DynamicAthlete was inheriting from DynamicBase<T>. DynamicBase<T> is no Super Bass-O-Matic ’76, but it slices and dices classes pretty well too!

//Wrap an existing athlete
StaticAthlete staticAthlete = new StaticAthlete()
{
    Sport = "Hockey"
};

dynamic extendedAthlete = new DynamicBase<StaticAthlete>(staticAthlete);
extendedAthlete.Name = "Bobby Orr";
extendedAthlete.Position = "Defenseman";
extendedAthlete.GetUppercaseName =
        (Func<string>)(() =>
        {
            return ((string)extendedAthlete.Name).ToUpper();
        });
Console.WriteLine($"Static Athlete (extended):");
Console.WriteLine(extendedAthlete);
Console.WriteLine($"Uppercase Name: {extendedAthlete.GetUppercaseName()}");
Console.WriteLine();
Console.WriteLine();

You can see that the output for StaticAthlete is exactly the same as it was for the DynamicAthletes:

Static Athlete (extended):
Name:Bobby Orr
Sport:Hockey
Position:Defenseman

Uppercase Name: BOBBY ORR

Discussion

DynamicObject acts as a base class to help you add dynamic behaviors to your classes. Unlike ExpandoObject it cannot be instantiated, but it can be derived from. With DynamicObject, you can override many different types of operations, such as property or method access or any binary, unary, or type conversion operations, which allows you the flexibility to determine how the class will react at runtime.

We do some of these things in DynamicBase<T> by overriding the following methods on DynamicObject:

  • TryInvokeMember

  • GetDynamicMemberNames

  • TryGetMember

  • TrySetMember

TryInvokeMember allows us to determine what should happen when a member is invoked on the object. We use it in DynamicBase<T> to look at the internal collection and if we have a matching item, we invoke it dynamically as a delegate:

    public override bool TryInvokeMember(InvokeMemberBinder binder, 
        object[] args,
out object result)
    {
        if (_dynamicMembers.ContainsKey(binder.Name) &&
            _dynamicMembers[binder.Name] is Delegate)
        {
            result = (_dynamicMembers[binder.Name] as Delegate).DynamicInvoke(
                args);
            return true;
        }

        return base.TryInvokeMember(binder, args, out result);
    }

GetDynamicMemberNames gets the set of all members that were added dynamically:

public override IEnumerable<string> GetDynamicMemberNames()
{
    return _dynamicMembers.Keys;
}

TryGetMember is overridden to allow the caller to get property values for the items that have been added dynamically. If we don’t find it in the main property information for the class, we look in the internal dictionary of dynamic members and return it from there:

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
    result = null;
    var propertyInfo = _propertyInfos.Where(pi =>
        pi.Name == binder.Name).FirstOrDefault();
    // Make sure this member isn't a property on the object yet
    if (propertyInfo == null)
    {
        // look in the additional items collection for it
        if (_dynamicMembers.Keys.Contains(binder.Name))
        {
            // return the dynamic item
            result = _dynamicMembers[binder.Name];
            return true;
        }
    }
    else
    {
        // get it from the contained object
        if (_containedObject != null)
        {
            result = propertyInfo.GetValue(_containedObject);
            return true;
        }
    }
    return base.TryGetMember(binder, out result);
}

The override for TrySetMember handles when a property value is being set. Once again, we look at the typed object first and then look to the dynamic dictionary for where to store the value:

public override bool TrySetMember(SetMemberBinder binder, object value)
{
    var propertyInfo = _propertyInfos.Where(pi =>
        pi.Name == binder.Name).FirstOrDefault();
    // Make sure this member isn't a property on the object yet
    if (propertyInfo == null)
    {
        // look in the additional items collection for it
        if (_dynamicMembers.Keys.Contains(binder.Name))
        {
            // set the dynamic item
            _dynamicMembers[binder.Name] = value;
            return true;
        }
        else
        {
            _dynamicMembers.Add(binder.Name, value);
            return true;
        }
    }
    else
    {
        // put it in the contained object
        if (_containedObject != null)
        {
            propertyInfo.SetValue(_containedObject, value);
            return true;
        }
    }
    return base.TrySetMember(binder, value);
}

We have also overridden ToString so that we can get all of the properties (static and dynamic) on the class to be represented in the string:

public override string ToString()
{
    StringBuilder builder = new StringBuilder();
    foreach (var propInfo in _propertyInfos)
    {
        if(_containedObject != null)
            builder.AppendFormat("{0}:{1}{2}", propInfo.Name,
                propInfo.GetValue(_containedObject), Environment.NewLine);
        else
            builder.AppendFormat("{0}:{1}{2}", propInfo.Name,
                propInfo.GetValue(this), Environment.NewLine);
    }
    foreach (var addlItem in _dynamicMembers)
    {
        // exclude methods that are added from the description
        Type itemType = addlItem.Value.GetType();
        Type genericType =
            itemType.IsGenericType ? itemType.GetGenericTypeDefinition() : null;
        if (genericType != null)
        {
            if (genericType != typeof(Func<>) &&
                genericType != typeof(Action<>))
                builder.AppendFormat("{0}:{1}{2}", addlItem.Key, addlItem.Value,
                    Environment.NewLine);
        }
        else
            builder.AppendFormat("{0}:{1}{2}", addlItem.Key, addlItem.Value,
                Environment.NewLine);
    }
    return builder.ToString();
}

We do a bit of filtering to handle the cases where dynamic methods or events are added and when the member or method is on the contained object. This allows us to get the representation of all properties like this:

Name:Bobby Orr
Sport:Hockey
Position:Defenseman

As you can see, DynamicObject gives you all the power you need to extend your objects as far as you want to take them.

See Also

The “DynamicObject Class” topic in the MSDN documentation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.23.103.112