What You Will Learn in this Chapter
WROX.COM CODE DOWNLOADS FOR THIS CHAPTER
You can find the code downloads for this chapter at www.wrox.com/remtitle.cgi?isbn=1118612094 on the Download Code tab. The code is in the chapter08 download and individually named according to the names throughout the chapter.
This chapter has a mix of topics that will be covered on the exam, and these four topics do not directly relate to each other. The first three topics cover features that can be used to examine, customize, or generate C# code, while the last topic explains how to write shorthand syntax for your existing methods. It is especially important to understand the concept of lambda expressions because not only will you see them used extensively in Chapter 10, “Working with Language Integrated Query (LINQ),” but you will see questions on the test that use lambda expressions and you could be asked what the result of the expression would be.
Table 8-1 introduces you to the exam objectives covered in this chapter.
Objective | Content Covered |
Create and use types | Reflection. This includes finding, executing, and creating types at run time. Attributes. This includes creating, applying, and reading attributes that can be used to change the behavior of your class. The CodeDOM. This includes creating code generators. Lambda expressions. This is shorthand syntax for creating methods without the normal method declaration syntax. |
Reflection refers to the ability to examine code and dynamically read, modify, or invoke behavior for an assembly, module, or type. A type is any class, interface, array, value type, enumeration, parameter, generic type definition, and open or closed constructed generic types. You can use the classes in the System.Reflection namespace and the System.Type class to discover the assembly name, the namespace, the properties, the methods, the base class, and plenty of other metadata about a class or variable.
The System.Reflection namespace contains numerous classes that can be used to read metadata or dynamically invoke behavior from a type. Table 8-2 lists some of the frequently used classes in the System.Reflection namespace.
Type | Description |
Assembly | Represents a DLL or EXE file and contains properties for the Assembly name, classes, modules, and other metadata language run-time application. |
EventInfo | Represents an event defined in your class and contains properties such as the event name. |
FieldInfo | Represents a field defined in your class and contains properties such as whether the field is public or private. |
MemberInfo | Abstracts the metadata about a class and can represent an event, a field, and so on. |
MethodInfo | Represents a method defined in your class and can be used to invoke the method. |
Module | The module is a file that composes the assembly. This is usually a DLL or EXE file. |
ParameterInfo | Represents a parameter declaration for a method or a constructor. This allows you to determine the type of parameter, its name, as well as other properties. |
PropertyInfo | Represents a property defined in your class and contains properties such as the property name and type. |
Reflection is a powerful feature and can be used with some design patterns such as the Factory or Inversion of Control design patterns. These design patterns are more advanced topics and won’t be covered in the test, but it is important to understand the concept of reflection and its capabilities for the test.
An assembly is essentially a compiled piece of code that is typically a DLL or EXE file. You can use the Assembly class to load the assembly, read metadata about the assembly, and even create instances of the types contained in the assembly. Table 8-3 lists frequently used properties for an Assembly.
Property | Description |
CodeBase | Returns the path for the assembly |
DefinedTypes | Returns a collection of the types defined in this assembly |
ExportedTypes | Returns a collection of the public types defined in this assembly |
FullName | Returns the name of the assembly |
GlobalAssemblyCache | Returns a boolean value indicating whether the assembly was loaded from the global assembly cache |
ImageRuntimeVersion | Returns the version of the Common Language Runtime (CLR) for the assembly |
Location | Returns the path or UNC location of the assembly |
Modules | Returns a collection that contains the modules in this assembly |
SecurityRuleSet | Returns a value that indicates which set of security rules the Common Language Runtime enforces for the assembly |
The following code sample loads the System.Data assembly and writes some of the assembly’s properties to the Output window:
Assembly myAssembly = Assembly.Load("System.Data, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089");
Debug.WriteLine("CodeBase: {0}", myAssembly.CodeBase);
Debug.WriteLine("FullName: {0}", myAssembly.FullName);
Debug.WriteLine("GlobalAssemblyCache: {0}", myAssembly.GlobalAssemblyCache);
Debug.WriteLine("ImageRuntimeVersion: {0}", myAssembly.ImageRuntimeVersion);
Debug.WriteLine("Location: {0}", myAssembly.Location);
The preceding code produces the following output:
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_32/System.Data/
v4.0_4.0.0.0__b77a5c561934e089/System.Data.dll
FullName: System.Data, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
GlobalAssemblyCache: True
ImageRuntimeVersion: v4.0.30319
Location: C:WindowsMicrosoft.NetassemblyGAC_32System.Data
v4.0_4.0.0.0__b77a5c561934e089System.Data.dll
Table 8-4 lists the frequently used methods for an Assembly.
Method | Description |
CreateInstance(String) | Creates an instance of the class by searching the assembly for the class name |
GetCustomAttributes(Boolean) | Returns an array of objects that represent the custom attributes for this assembly |
GetExecutingAssembly | Returns an Assembly object for the currently running program |
GetExportedTypes | Returns the public classes defined in this assembly |
GetModule | Returns the specified module in this assembly |
GetModules() | Returns all the modules that are part of this assembly |
GetName() | Returns the AssemblyName for this assembly |
GetReferencedAssemblies | Returns an array of AssemblyName objects that represent all referenced assemblies |
GetTypes | Returns an array of Type object defined in this assembly |
Load(String) | Loads an assembly given the long form of its name |
LoadFile(String) | Loads the contents of an assembly file given a file path |
LoadFrom(String) | Loads an assembly given its file name or path |
ReflectionOnlyLoad(String) | Loads an assembly but you can only perform reflection on the types defined in the assembly |
UnsafeLoadFrom | Loads an assembly bypassing some security checks |
The GetExecutingAssembly method is a static method that enables you to get a reference to the currently executing code. The GetExportedTypes and GetTypes methods are all used to get references to the types defined in the assembly. (The System.Type class will be explained in more detail in the next section.) The difference between GetExportedTypes and GetTypes is that the GetExportedTypes returns only the types that are public. The following code snippet displays all the types defined in the currently executing assembly:
Assembly myAssembly = Assembly.GetExecutingAssembly();
Type[] myAssemblysTypes = myAssembly.GetTypes();
foreach (Type myType in myAssemblysTypes)
{
Debug.WriteLine(string.Format("myType Name: {0}", myType.Name));
}
The Modules property or the GetLoadedModules, GetModules, and GetModule methods return the list of modules or a specific module defined in the assembly. A module is a file that composes the Assembly. This is usually a single DLL or EXE file. The following code lists all the modules defined in the System.Data assembly:
Assembly myAssembly = Assembly.Load("System.Data, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089");
Module[] myAssemblysModules = myAssembly.GetModules();
foreach (Module myModule in myAssemblysModules)
{
Debug.WriteLine(string.Format("myModule Name: {0}", myModule.Name));
}
The output for the preceding code snippet is as follows:
myModule Name: System.Data.dll
The preceding code snippet used the Load method to load the System.Data assembly into memory. Because the assembly was loaded using the Load method, you can then execute code within the assembly. If you do not need to execute code, you can use the ReflectionOnlyLoad method.
You can also load an assembly by calling the LoadFrom or LoadFile methods. Both methods take a file path as a parameter. To understand the difference between LoadFrom and LoadFile, you need to understand the concept of context and the different types of context there are. Think of the context as where reflection searches for the assembly. An assembly can be in one of three contexts or no context at all:
Once you have an assembly loaded you can then create instances of the classes defined in the assembly. To create an instance of a class, use the CreateInstance method. The following code creates an instance of the DataTable and prints the number of rows to the Output window:
Assembly myAssembly = Assembly.Load("System.Data, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089");
DataTable dt = (DataTable)myAssembly.CreateInstance("System.Data.DataTable");
Debug.Print("Number of rows: {0}", dt.Rows.Count);
The GetReferencesAssemblies is used to discover the references for the assembly. This can be helpful when troubleshooting deployment issues. The following code prints all the referenced assemblies for the System.Data assembly:
Assembly myAssembly = Assembly.Load("System.Data, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089");
AssemblyName[] referencedAssemblyNames = myAssembly.GetReferencedAssemblies();
foreach (AssemblyName assemblyName in referencedAssemblyNames)
{
Debug.WriteLine("Assembly Name: {0}", assemblyName.Name);
Debug.WriteLine("Assembly Version: {0}", assemblyName.Version);
}
The preceding code will produce the following results:
Assembly Name: mscorlib
Assembly Version: 4.0.0.0
Assembly Name: System
Assembly Version: 4.0.0.0
Assembly Name: System.Xml
Assembly Version: 4.0.0.0
Assembly Name: System.Configuration
Assembly Version: 4.0.0.0
Assembly Name: System.Transactions
Assembly Version: 4.0.0.0
Assembly Name: System.Numerics
Assembly Version: 4.0.0.0
Assembly Name: System.EnterpriseServices
Assembly Version: 4.0.0.0
Assembly Name: System.Core
Assembly Version: 4.0.0.0
The GetCustomAttributes and GetCustomAttributesData will be explained later in this chapter in the “Read and Create Custom Attributes” section. But for now just realize that you can use the Assembly class to get the list of custom attribute classes defined in an assembly.
The System.Type class represents a class, interface, array, value type, enumeration, parameter, generic type definitions, and open or closed constructed generic types. For the most part, you usually use a Type to get information about a class contained in an assembly. You can obtain a reference to a type in two ways. You can use the typeof() keyword and pass in the name of the type:
System.Type myType = typeof(int);
Or you can use the GetType() method on an instance of the type:
int myIntVariable = 0;
System.Type myType = myIntVariable.GetType();
Both of these examples create an instance of a type class for the int type. After you have a reference to the type, you can then examine the properties. Table 8-5 lists commonly used properties for the System.Type class.
Property | Description |
Assembly | Returns the Assembly in which the type is declared |
AssemblyQualifiedName | Returns a string which is the assembly-qualified name of the Type, which includes the name of the assembly from which the Type was loaded |
BaseType | Return a Type from which the current Type inherits |
FullName | Returns a string which is the fully qualified name of the Type, including the namespace of the Type but not the assembly |
IsAbstract | Returns a boolean value indicating whether the Type is abstract |
IsArry | Returns a boolean value indicating whether the Type is an array |
IsClass | Returns a boolean value indicating whether the Type is a class rather than a value type or interface |
IsEnum | Returns a boolean value indicating whether the current Type represents an enumeration |
IsInterface | Returns a boolean value indicating whether the Type is an interface |
IsNotPublic | Returns a boolean value indicating whether the Type is not declared public |
IsPublic | Returns a boolean value indicating whether the Type is declared as public |
IsSerializable | Returns a boolean value indicating whether the Type is serializable |
IsValueType | Returns a boolean value indicating whether the Type is a value type |
Name | Returns the name of the type |
Namespace | Returns the namespace of the current type |
The following code creates an instance of an int variable, obtains a reference to the int type, and writes some of its properties to the Output window:
int myIntVariable = 0;
System.Type myType = myIntVariable.GetType();
Debug.WriteLine("AssmeblyQualifiedName: {0}", myType.AssemblyQualifiedName);
Debug.WriteLine("FullName: {0}", myType.FullName);
Debug.WriteLine("IsValueType: {0}", myType.IsValueType);
Debug.WriteLine("Name: {0}", myType.Name);
Debug.WriteLine("Namespace: {0}", myType.Namespace);
The preceding code produces the following output:
AssmeblyQualifiedName: System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
FullName: System.Int32
IsValueType: True
Name: Int32
Namespace: System
The System.Type class also has methods that you can use to get the properties, methods, constructors, interfaces, events and any other metadata about the type. Table 8-6 lists some of the methods of the type class.
Name | Description |
GetArrayRank | When the type represents an array, this returns the number of dimensions in an Array. |
GetConstructor(Type[]) | Searches for a public instance constructor whose parameters match the types in the specified array and returns a ConstructorInfo object. |
GetConstructors() | Returns an array of ConstructorInfo objects for all the public constructors defined for the current Type. |
GetEnumName | When the type represents an enumeration, this returns the name of the element that has the specified value. |
GetEnumNames | When the type represents an enumeration, this returns the all of the names of the members. |
GetEnumValues | When the type represents an enumeration, this returns an array of the values of the enumeration. |
GetField(String) | Searches for the public field with the specified name. |
GetFields() | Returns all the public fields of the current Type. |
GetInterface(String) | Returns the interface with the specified name. |
GetMember(String) | Returns the public member with the specified name. |
GetMembers() | Returns all public members of the current Type. |
GetMethod(String) | Returns the public method with the specified name. |
GetMethods() | Returns all the public methods of the current Type. |
GetProperties() | Returns all the public properties of the current Type. |
GetProperty(String) | Returns the public property with the specified name. |
GetTypeArray | Returns all the types of objects in the specified array. |
InvokeMember(String, BindingFlags, Binder, Object, Object[]) | Executes a method using the specified binding constraints and matching the specified argument list. |
When the Type object represents an array, the GetArrayRank method returns the number of dimensions in the array. The following code creates a three-dimensional array and prints the array rank to the Output window:
int[,,] myIntArray = new int[5,6,7];
Type myIntArrayType = myIntArray.GetType();
Debug.Print("Array Rank: {0}", myIntArrayType.GetArrayRank());
The preceding code prints the following to the Output window:
Array Rank: 3
The GetConstructors method returns an array of ConstructorInfo objects that you can use to get information about all the constructors of the type. The following code prints the constructors and the parameters for a System.DataTable object to the Output window:
DataTable myDataTable = new DataTable();
Type myDataTableType = myDataTable.GetType();
ConstructorInfo[] myDataTableConstructors = myDataTableType.GetConstructors();
for(int i = 0; i <= myDataTableConstructors.Length - 1; i++)
{
ConstructorInfo constructorInfo = myDataTableConstructors[i];
Debug.Print("
Constructor #{0}", i + 1);
ParameterInfo[] parameters = constructorInfo.GetParameters();
Debug.Print("Number Of Parameters: {0}", parameters.Length);
foreach (ParameterInfo parameter in parameters)
{
Debug.Print("Parameter Name: {0}", parameter.Name);
Debug.Print("Parameter Type: {0}",
parameter.ParameterType.Name);
}
}
The preceding code produces the following output:
Constructor #1
Number Of Parameters: 0
Constructor #2
Number Of Parameters: 1
Parameter Name: tableName
Parameter Type: String
Constructor #3
Number Of Parameters: 2
Parameter Name: tableName
Parameter Type: String
Parameter Name: tableNamespace
Parameter Type: String
When the Type object represents an enumeration, the GetEnum methods enable you to determine all the names and values within an enumeration. For example, the following is a custom enumeration with three members:
private enum MyCustomEnum
{
Red = 1,
White = 2,
Blue = 3
}
The following code writes all the names in the enumeration to the Output window:
Type myCustomEnumType = typeof(MyCustomEnum);
string[] enumNames = myCustomEnumType.GetEnumNames();
foreach (string enumName in enumNames)
{
Debug.Print(string.Format("Name: {0}", enumName));
}
The preceding code displays the following in the Output window:
Name: Red
Name: White
Name: Blue
You can obtain the values for the enumeration by using the GetEnumValues method. The following code prints all the enumerations values to the Output window:
Type myCustomEnumType = typeof(MyCustomEnum);
Array enumValues = myCustomEnumType.GetEnumValues();
foreach (object enumValue in enumValues)
{
Debug.Print(string.Format("Enum Value: {0}", enumValue.ToString()));
}
The preceding code produces the following output:
Enum Value: Red
Enum Value: White
Enum Value: Blue
You can also use the GetEnumName method to retrieve the name. The following displays all the members by using the underlying value of the enumeration.
Type myCustomEnumType = typeof(MyCustomEnum);
for (int i = 1; i <= 3; i++)
{
string enumName = myCustomEnumType.GetEnumName(i);
Debug.Print(string.Format("{0}: {1}", enumName, i));
}
The preceding code produces the following output.
Red: 1
White: 2
Blue: 3
A field is a variable defined in a class or struct. The GetField method is used to get a FieldInfo object for one field. The GetFields method returns an array of FieldInfo objects. The GetFields method can also return the fields from inherited classes. When you call GetFields, you pass in the BindingFlags enumeration to specify the scope of fields that you want it to return. For example, the following class contains five fields all with different scope:
class ReflectionExample
{
private string _privateField = "Hello";
public string _publicField = "Goodbye";
internal string _internalfield = "Hola";
protected string _protectedField = "Adios";
static string _staticField = "Bonjour";
}
You can use GetFields to get the values of these variables regardless of the scope.
ReflectionExample reflectionExample = new ReflectionExample();
Type reflectionExampleType = typeof(ReflectionExample);
FieldInfo[] fields = reflectionExampleType.GetFields(BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.NonPublic |
BindingFlags.FlattenHierarchy);
foreach (FieldInfo field in fields)
{
object fieldValue = field.GetValue(reflectionExample);
Debug.WriteLine(string.Format("Field Name: {0}, Value: {1}", field.Name,
fieldValue.ToString()));
}
The preceding code produces the following output:
Field Name: _privateField, Value: Hello
Field Name: _publicField, Value: Goodbye
Field Name: _internalfield, Value: Hola
Field Name: _protectedField, Value: Adios
Field Name: _staticField, Value: Bonjour
When calling GetFields, you use the BindingFlags enumeration and can specify more than one value by using the bitwise operator.
The FieldInfo object also has a SetValue method that enables you to change the value of the field, even if it is private or protected. To demonstrate, add the following get accessor to the ReflectionExample class:
public string PrivateField
{
get { return privateField; }
}
The following code changes the value of the privateField variable and prints its value to the Output window:
ReflectionExample reflectionExample = new ReflectionExample();
Type reflectionExampleType = typeof(ReflectionExample);
reflectionExampleType.GetField("privateField", BindingFlags.NonPublic |
BindingFlags.Instance).SetValue(reflectionExample, "My New Value");
Debug.Print("Private Field Value: {0}",
reflectionExample.PrivateField);
The preceding code produces the following output:
Private Field Value: My New Value
The GetProperty and GetProperties methods are similar to the GetField and GetFields methods because they enable you to get the properties, get their value, or set their value. The difference is that you use a PropertyInfo object instead of the FieldInfo object, and you can access only properties instead of fields. The PropertyInfo object has GetValue and SetValue methods just like the FieldInfo object and works the same way.
The GetMethod and GetMethods methods enable you to obtain information about a method for a type. After you have a reference to the method, you can execute the method by calling the Invoke method of the MethodInfo class. You can also execute the method by calling the InvokeMember method of the Sytem.Type class and pass in the name of the method and its parameters.
Add the following method to the ReflectionExample class:
public double Multiply(double x, double y)
{
return x * y;
}
The following code calls the Multiply method and prints the return value to the Output window:
ReflectionExample reflectionExample = new ReflectionExample();
Type reflectionExampleType = typeof(ReflectionExample);
MethodInfo methodInfo = reflectionExampleType.GetMethod("Multiply");
double returnValue = (double)methodInfo.Invoke(reflectionExample,
new object[] { 4, 5 });
Debug.Print("Return Value: {0}", returnValue);
Notice that you pass parameters to the method by creating an array of objects and passing it as the second parameter to the Invoke method. The preceding code prints “Return Value: 20” to the Output window.
When using the InvokeMember method of the System.Type class, the syntax is as follows:
ReflectionExample reflectionExample = new ReflectionExample();
Type reflectionExampleType = typeof(ReflectionExample);
double returnValue = (double)reflectionExampleType.InvokeMember("Multiply",
BindingFlags.InvokeMethod,
null,
reflectionExample,
new object[] { 4, 5 });
Debug.Print(string.Format("Return Value: {0}", returnValue));
The second parameter is BindingFlags.InvokeMethod, which triggers the InvokeMember method to invoke the method.
class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
public static bool LoadClassFromSQLDataReader(object myClass, SqlDataReader dr)
{
if (dr.HasRows)
{
dr.Read();
Type typeOfClass = myClass.GetType();
for (int columnIndex = 0; columnIndex <= dr.FieldCount - 1; columnIndex++)
{
//Get the name of the column
string columnName = dr.GetName(columnIndex);
//Check if a property exists that matches that name.
PropertyInfo propertyInfo = typeOfClass.GetProperty(columnName);
if (propertyInfo != null)
{
//Set the value to the value in the SqlDataReader
propertyInfo.SetValue(myClass, dr.GetValue(columnIndex));
}
}
return true;
}
else
{
return false;
}
}
public bool GetPerson(int personId)
{
//Open the connection to the database.
SqlConnection cn = new
SqlConnection("Server=(local);Database=Reflection;Trusted_Connection=True;");
cn.Open();
//Retrieve the record.
SqlCommand cmd = new SqlCommand(
string.Format("SELECT * FROM Person WHERE PersonId = {0}", personId), cn);
SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
return ReflectionExample.LoadClassFromSQLDataReader(this, dr);
}
Attributes enable you to define metadata for a class, a property, or a method. The class, property, or method is referred to as the target of the attribute. Reflection can then be used to read these attributes dynamically and change how the target behaves. Attributes are contained in square brackets “[ ]” above the target and can be stacked on top of each other when multiple attributes are needed to define the target. For example, if you want to make a class serializable, you need to add the [Serializable()] attribute above the class declaration:
[Serializable()]
public class MyClass
{
…
}
The Serializable attribute is actually a class defined in the .NET Framework that inherits from System.Attribute. The System.Attribute class is an abstract class that is the base class for all custom attributes. This section explains how to read attributes using reflection and then create your own custom attributes.
In the previous section you learned about the System.Reflection namespace and the numerous classes defined in the namespace that let you read the metadata about the classes within an assembly. An assembly has a GetCustomAttributes method that enables you to enumerate through all the custom attributes classes contained in the assembly or filter the specific type of attribute you would like to retrieve. The following code block iterates through all the referenced assemblies for the currently executing assembly and prints the custom attributes class names and properties to the Output window:
Assembly assembly = Assembly.GetExecutingAssembly();
AssemblyName[] assemblyNames = assembly.GetReferencedAssemblies();
foreach (AssemblyName assemblyName in assemblyNames)
{
Debug.WriteLine("
Assembly Name: {0}", assemblyName.FullName);
Assembly referencedAssembly = Assembly.Load(assemblyName.FullName);
object[] attributes = referencedAssembly.GetCustomAttributes(false);
foreach (object attribute in attributes)
{
Debug.WriteLine(
Attribute Name: {0}",
attribute.GetType().Name);
//Get the properties of this attribute
PropertyInfo[] properties = attribute.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
Debug.WriteLine("{0} : {1}", property.Name,
property.GetValue(attribute));
}
}
}
The following text is a partial list of the output from the preceding code. The currently executing assembly referenced three assemblies: mscorlib, System.Data, and System. Each has their own set of custom attribute classes.
Assembly Name: mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
Attribute Name: StringFreezingAttribute
TypeId : System.Runtime.CompilerServices.StringFreezingAttribute
…
Assembly Name: System.Data, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
Attribute Name: AllowPartiallyTrustedCallersAttribute
PartialTrustVisibilityLevel : VisibleToAllHosts
TypeId : System.Security.AllowPartiallyTrustedCallersAttribute
Attribute Name: CLSCompliantAttribute
IsCompliant : True
TypeId : System.CLSCompliantAttribute
Attribute Name: RuntimeCompatibilityAttribute
WrapNonExceptionThrows : True
TypeId : System.Runtime.CompilerServices.RuntimeCompatibilityAttribute
…
Assembly Name: System, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
Attribute Name: ComVisibleAttribute
Value : False
TypeId : System.Runtime.InteropServices.ComVisibleAttribute
Notice that the GetCustomAttributes method returns an array of objects. This is because each attribute is its own class. The first attribute in the mscorlib assembly is the StringFreezingAttribute. This is a class that inherits from System.Attribute and can have its own properties and methods. As you read through the preceding output, you can see that each attribute class has its own custom properties and also the properties of the System.Attribute class.
To create your own custom attribute, you need to create a class that inherits from the System.Attribute abstract class:
class MyCustomAttribute : System.Attribute
{
}
After you declare your class, you can then add properties and enumerations to the class just like any other class. For this example, you add one enumeration and three properties:
public enum MyCustomAttributeEnum
{
Red,
White,
Blue
}
public bool Property1 { get; set; }
public string Property2 { get; set; }
public MyCustomAttributeEnum Property3 { get; set; }
The next step is to define the scope of the attribute. For this example you limit the scope of this attribute so that the target can be only a class or a struct. To do this you use the System.AttributeUsage custom attribute. This attribute is applied to the class:
[System.AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
class MyCustomAttribute : System.Attribute
{
…
}
Now you can use this attribute when defining a class. Create a new class named MyTestClass. Above the class declaration add the following code:
[MyCustom(Property1 = true, Property2 = "Hello World", Property3 =
MyCustomAttribute.MyCustomAttributeEnum.Red)]
class MyTestClass()
{
}
Notice that the name of the attribute is MyCustom, not MyCustomAttribute. To set the properties of the attribute, you pass the values to the constructor as named parameters. You could have also added a constructor to the MyCustomAttribute class with three parameters and then set the property values in the body of the constructor.
Now that you have the class with a custom attribute, you can use reflection to read the attribute values. The following code can read the custom attributes for the MyTestClass:
Type myTestClassType = typeof(MyTestClass);
MyCustomAttribute attribute =
(MyCustomAttribute)myTestClassType.GetCustomAttribute
(
typeof(MyCustomAttribute),
false
);
Debug.WriteLine("Property1: {0}", attribute.Property1);
Debug.WriteLine("Property2: {0}", attribute.Property2);
Debug.WriteLine("Property3: {0}", attribute.Property3);
The preceding code produces the following output:
Property1: True
Property2: Hello World
Property3: Red
[System.AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]
class DataMappingAttribute : System.Attribute
{
}
public string ColumnName { get; set; }
public string PropertyName { get; set; }
public DataMappingAttribute(string columnName, string propertyName)
{
ColumnName = columnName;
PropertyName = propertyName;
}
[DataMapping("FirstName", "FName")]
[DataMapping("LastName", "LName")]
class Person
{
…
}
public static bool LoadClassFromSQLDataReader(object myClass, SqlDataReader dr)
{
if (dr.HasRows)
{
dr.Read();
Type typeOfClass = myClass.GetType();
object[] dataMappingAttributes =
typeOfClass.GetCustomAttributes(typeof(DataMappingAttribute), false);
for (int columnIndex = 0; columnIndex <= dr.FieldCount - 1; columnIndex++)
{
//Get the name of the column.
string columnName = dr.GetName(columnIndex);
//Check if a property exists that matches that name.
PropertyInfo propertyInfo = null;
//Check if an attribute exists that maps this column to a property.
foreach (DataMappingAttribute dataMappingAttribute in
dataMappingAttributes)
{
if (dataMappingAttribute.ColumnName == columnName)
{
propertyInfo =
typeOfClass.GetProperty(dataMappingAttribute.PropertyName);
break;
}
}
//The the property was mapped explicitely then try to find a
//property with the same name as the column.
if (propertyInfo == null)
{
propertyInfo = typeOfClass.GetProperty(columnName);
}
//If you found a property then set its value.
if (propertyInfo != null)
{
//Set the value to the value in the SqlDataReader
propertyInfo.SetValue(myClass, dr.GetValue(columnIndex));
}
}
return true;
}
else
{
return false;
}
}
The Code Document Object Model, CodeDOM, is a set of classes in the .NET Framework that enables you to create code generators. A code generator is a program that can write your code for you. The CodeDOM is an object model that contains classes that represent properties, methods, classes, boolean logic, parameters, and any other type of code element you can write in your program. You can use the CodeDOM classes to write your code generically and then have it generated in C#, VB.NET, or JScript.
Have you ever written classes that have properties that map to a table? This can be tedious and time-consuming. This is a great example of when you would want to use the CodeDOM to generate this code for you automatically. You can create a program that reads the columns from a table in a database and creates a class that contains a column for each property. This section explains the classes within the CodeDOM namespace and shows you how to generate code for classes, properties, and methods, and even generate looping structures and if statements.
The classes in the CodeDOM model the code statements, such as an if statement or variable declaration. You can write a code generator using the CodeDOM classes and then generate your code in either C#, VB.NET, or JScript. The CodeDOM classes are great for automating repetitive coding tasks or enforcing patterns within your projects. This section will demonstrate how to create a class file using the CodeDOM that contains fields, properties, and methods.
The typical structure of a class within C# contains the following elements:
The CodeDOM namespace contains classes that enable you to create a structure called a CodeDOM Graph that models these elements. Table 8-7 lists some of the classes you can use in the CodeDOM namespace.
Name | Description |
CodeArgumentReferenceExpression | Represents a reference to the value of an argument passed to a method |
CodeAssignStatement | Represents an assignment statement |
CodeBinaryOperatorExpression | Represents an expression that consists of a binary operation |
CodeCastExpression | Represents an expression that casts to another data type or interface |
CodeComment | Represents a comment |
CodeCompileUnit | Provides a container for a CodeDOM, which contains the declarations, namespace, class, and components of your class |
CodeConditionStatement | Represents a conditional statement, typically represented as an if statement |
CodeConstructor | Represents a declaration of an instance constructor |
CodeFieldReferenceExpression | Represents a reference to a field |
CodeIterationStatement | Represents a for statement or other looping structure |
CodeMemberEvent | Represents a declaration for an event |
CodeMemberField | Represents a declaration for a field |
CodeMemberMethod | Represents a declaration for a method |
CodeMemberProperty | Represents a declaration for a property |
CodeMethodInvokeExpression | Represents an expression that invokes a method |
CodeMethodReturnStatement | Represents a return value statement |
CodeNamespace | Represents a namespace declaration |
CodeNamespaceImport | Represents a namespace import directive |
CodeObjectCreateExpression | Represents an expression that creates a new instance of a type |
CodeParameterDeclarationExpression | Represents a parameter declaration for a method, property, or constructor |
CodePropertyReferenceExpression | Represents a reference to the value of a property |
CodePropertySetValueReferenceExpression | Represents the value argument of a property set method |
CodeRegionDirective | Specifies the name and mode for a code region |
CodeSnippetCompileUnit | Represents a literal code fragment that can be compiled |
CodeSnippetStatement | Represents a statement using a literal code fragment |
CodeThisReferenceExpression | Represents a reference to the current local class instance |
CodeThrowExceptionStatement | Represents a statement that throws an exception |
CodeTryCatchFinallyStatement | Represents a try, catch, and finally block |
CodeTypeConstructor | Represents a static constructor for a class |
CodeTypeDeclaration | Represents a declaration for a class, structure, interface, or enumeration |
CodeTypeDelegate | Represents a delegate declaration |
CodeVariableDeclarationStatement | Represents a variable declaration |
CodeVariableReferenceExpression | Represents a reference to a local variable |
As you can see from the list of classes in Table 8-6, the CodeDOM has classes for every type of statement you can make in the .NET Framework. You can then choose to have the code rendered in the language of your choice. For example, look at the following class and then later in the chapter you will use the CodeDOM to generate this class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Reflection
{
class Calculator
{
private double x;
private double y;
public double X
{
get { return this.x; }
set { this.x = value; }
}
public double Y
{
get { return this.y; }
set { this.y = value; }
}
public double Divide()
{
if (this.Y == 0)
{
return 0;
}
else
{
return this.X / this.Y;
}
}
public double Exponent(double power)
{
return Math.Pow(this.X, power);
}
}
}
This is a simple class called Calculator that is in the Reflection namespace, contains two fields, two properties, and two methods. The following sections demonstrates which classes in the CodeDOM you should use to create the class dynamically.
The CodeCompileUnit class is the top-level class that is the container for all other objects within the class you want to generate. Think of this as the class that represents the file that contains your code. The following code is used to create an instance of the CodeCompileUnit class:
CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
The next step is to add the namespace. The CodeNamspace class is used to represent the namespace. The constructor takes the namespace as the parameter.
CodeNamespace codeNamespace = new CodeNamespace("Reflection");
Now that you have a namespace, you can append the using statements. Normally, when you create a class file, the using statements are above the namespace declaration, but they still work if you add them after the namespace. The CodeNamespaceImport class is used to define the namespace you would like to import. In C# you use the using keyword, but in VB.NET you would use the imports keyword. By using the CodeDOM, you don’t have to worry about the correct keyword.
codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
codeNamespace.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
codeNamespace.Imports.Add(new CodeNamespaceImport("System.Linq"));
codeNamespace.Imports.Add(new CodeNamespaceImport("System.Text"));
codeNamespace.Imports.Add(new CodeNamespaceImport("System.Threading.Tasks"));
The preceding code produces the following output:
namespace Reflection
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
}
The next step is to declare the class. This is done by using the CodeTypeDeclaration class.
CodeTypeDeclaration targetClass = new CodeTypeDeclaration("Calculator");
targetClass.IsClass = true;
targetClass.TypeAttributes = TypeAttributes.Public;
//Add the class to the namespace.
codeNamespace.Types.Add(targetClass);
The preceding code creates an instance of the CodeTypeDeclaration class and sets the IsClass attribute to true, which tells the .NET Framework to generate a class declaration. The TypeAttributes property enables you to define attributes such as public, private, and static. These can be combined using the bitwise operator (|). After the class is defined, you need to add it to the Types collection of the namespace. The preceding code produces the following output:
public class Calculator
{
}
The next step is to add the fields to the class. This is done by using the CodeMemberField class. You simply create an instance of the class and set its Name property, set the Type property, and add it to the Members collection of the CodeTypeDeclaration object. The following code creates two fields, _x and _y, both of which are declared as a double:
CodeMemberField xField = new CodeMemberField();
xField.Name = "x";
xField.Type = new CodeTypeReference(typeof(double));
targetClass.Members.Add(xField);
CodeMemberField yField = new CodeMemberField();
yField.Name = "y";
yField.Type = new CodeTypeReference(typeof(double));
targetClass.Members.Add(yField);
The preceding code produces the following output:
private double x;
private double y;
The next step is to create the properties for the x and y fields. You use a CodeMemberProperty class to create a property and generate the get and set methods. The following code creates the X and Y properties in the Calculator class:
//X Property
CodeMemberProperty xProperty = new CodeMemberProperty();
xProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
xProperty.Name = "X";
xProperty.HasGet = true;
xProperty.HasSet = true;
xProperty.Type = new CodeTypeReference(typeof(System.Double));
xProperty.GetStatements.Add(new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "x")));
xProperty.SetStatements.Add(new CodeAssignStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "x"),
new CodePropertySetValueReferenceExpression()));
targetClass.Members.Add(xProperty);
//Y Property
CodeMemberProperty yProperty = new CodeMemberProperty();
yProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
yProperty.Name = "Y";
yProperty.HasGet = true;
yProperty.HasSet = true;
yProperty.Type = new CodeTypeReference(typeof(System.Double));
yProperty.GetStatements.Add(new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "y")));
yProperty.SetStatements.Add(new CodeAssignStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "y"),
new CodePropertySetValueReferenceExpression()));
targetClass.Members.Add(yProperty);
The CodeMemberProperty class has two properties (HasGet and HasSet) that you need to set to true so the code generator can create the Get and Set accessors. The GetStatements collection property is used to add the code to the Get accessor. In this example, the Get method returns the this.x field. The CodeThisReferenceExpression class is used because in C# you use this; in VB you use Me. The code generator knows which keyword to use when you generate the code. The SetStatements collection property contains the code to set the this.x field. In this instance you need to create a CodeAssignStatement along with the CodePropertySetValueReferenceExpression. The preceding code produces the following output:
public double X
{
get
{
return this.x;
}
set
{
this.x = value;
}
}
public double Y
{
get
{
return this.y;
}
set
{
this.y = value;
}
}
The next step is to create the Divide method. To create methods using the CodeDOM, you need to use the CodeMemberMethod class. The following code creates an instance of the CodeMemberMethod class, names the method Divide, sets the return type to double, and sets its attributes to public and final. If you want to set other attributes, such as static, virtual, or new, you can use the bitwise operator to concatenate the attributes.
CodeMemberMethod divideMethod = new CodeMemberMethod();
divideMethod.Name = "Divide";
divideMethod.ReturnType = new CodeTypeReference(typeof(double));
divideMethod.Attributes = MemberAttributes.Public | MemberAttributes.Final;
Now that the method signature is defined, you need to create the code for the body of the method. The Divide method checks if the Y property is 0 and either returns 0 or the quotient. If logic is created by using the CodeConditonStatement class.
CodeConditionStatement ifLogic = new CodeConditionStatement();
ifLogic.Condition = new CodeBinaryOperatorExpression(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), yProperty.Name),
CodeBinaryOperatorType.ValueEquality,
new CodePrimitiveExpression(0));
ifLogic.TrueStatements.Add(new CodeMethodReturnStatement(
new CodePrimitiveExpression(0)));
ifLogic.FalseStatements.Add(new CodeMethodReturnStatement(
new CodeBinaryOperatorExpression(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), xProperty.Name),
CodeBinaryOperatorType.Divide,
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), yProperty.Name))));
divideMethod.Statements.Add(ifLogic);
As you can see the CodeConditonStatement class has a Condition property that is a CodeBinaryOperatorExpression class. This class is used to create a binary expression. In this example the expression equates to (this.Y == 0). The CodeBinaryOperatorExpression class also has a TrueStatements and a FalseStatements property that enables you to create the code that will be written for the true and false conditions. The preceding code creates the following output:
public double Divide()
{
if ((this.Y == 0))
{
return 0;
}
else
{
return (this.X / this.Y);
}
}
The next step is to create the Exponent method. This method takes a parameter called power and returns this.Y raised to that power.
CodeMemberMethod exponentMethod = new CodeMemberMethod();
exponentMethod.Name = "Exponent";
exponentMethod.ReturnType = new CodeTypeReference(typeof(double));
exponentMethod.Attributes = MemberAttributes.Public | MemberAttributes.Final;
CodeParameterDeclarationExpression powerParameter =
new CodeParameterDeclarationExpression(typeof(double), "power");
exponentMethod.Parameters.Add(powerParameter);
CodeMethodInvokeExpression callToMath = new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression("System.Math"),
"Pow",
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(),
xProperty.Name), new CodeArgumentReferenceExpression("power"));
exponentMethod.Statements.Add(new CodeMethodReturnStatement(callToMath));
targetClass.Members.Add(exponentMethod);
You use the CodeParameterDeclarationExpression class to create the power parameter. The CodeMethodInvokeExpression class is used to call a method and pass a parameter to the method. The preceding code produces the following output:
public double Exponent(double power)
{
return System.Math.Pow(this.X, power);
}
The last step is to generate the class file. You use the CodeDOMProvider class to create the file in either C#, VB, or JScript. This class has a method called GenerateCodeFromCompileUnit that takes a CodeCompileUnit, TextWriter, and CodeGeneratorOptions class as parameters. The CodeGeneratorOptions class has properties that enable you to control the formatting of your automatically generated code. The following sample tells the compiler to use single-line spacing between the member declarations. Setting the BracingStyle property to “C” places the brackets, {}, on separate lines.
CodeDOMProvider provider = CodeDOMProvider.CreateProvider("CSharp");
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BlankLinesBetweenMembers = false;
options.BracingStyle = "C";
using (StreamWriter sourceWriter = new StreamWriter(@"c:CodeDOMCalculator." +
provider.FileExtension))
{
provider.GenerateCodeFromCompileUnit(codeCompileUnit, sourceWriter, options);
}
Lambda expressions are shorthand syntax for creating an anonymous methods. What’s an anonymous method? Well, an anonymous method is essentially a method without a name. What good is a method without a name? Well, when writing methods that are small and used in limited scope, you can write an anonymous method without going through the trouble of creating the method signature. Also, you can pass an anonymous method to other methods to dynamically change how those methods behave. This concept is extremely important to understand before tackling the concept of LINQ. Lambda expressions are used everywhere in LINQ.
Before exploring lambda expressions start with the basics. A delegate is a type that references a method. When you declare a delegate, you specify the signature of the method that you want to reference. For example, create a new class called LambdaExpressions and add the following method that takes a string parameter and writes it to the console window:
static void WriteToConsoleForward(string stringToWrite)
{
Console.WriteLine("This is my string: {0}", stringToWrite);
}
If you want to reference this method, first create a delegate that has the same signature.
delegate void MyFirstDelegate(string s);
Notice that the return type is void and the parameter’s type is string which matches the signature of the WriteToConsoleForward method. Now that you have a delegate, you need to associate a variable of this type to the method.
MyFirstDelegate myFirstDelegate = new
MyFirstDelegate(LambdaExpressions.WriteToConsoleForward);
The myFirstDelegate variable essentially holds a reference to the method. You can now call the method by using the myFirstDelegate variable and passing in a parameter.
myFirstDelegate("Hello World");
Now create a second method that takes a string as a parameter and writes the string backward to the console.
static void WriteToConsoleBackwards(string stringToWrite)
{
char[] charArray = stringToWrite.ToCharArray();
Array.Reverse(charArray);
Console.WriteLine("This is my string backwards: {0}",
new string(charArray));
}
Both methods have the same signature, so you can create a single delegate to reference either method. Now create another method that takes the delegate as a parameter and calls the method.
static void WriteToConsole(MyFirstDelegate myDelegate, string stringToWrite)
{
myDelegate(stringToWrite);
}
Now you can call the WriteToConsole method and pass in the method as a parameter.
WriteToConsole(LambdaExpressions.WriteToConsoleForward, "Hello World");
WriteToConsole(LambdaExpressions.WriteToConsoleBackwards, "Hello World");
The preceding two lines of code produce the following output:
This is my string: Hello World
This is my string backwards: dlroW olleH
Anonymous methods are similar to delegates except you don’t have to create the method. You still create the delegate, but you can assign the method all within the same line of code.
MyFirstDelegate forward = delegate(string s2)
{
Console.WriteLine("This is my string: {0}", s2);
};
forward("Hello World");
The preceding code creates a delegate variable called forward, and it references the body of a method. The method can have as many lines as you want. One difference between an anonymous method and a delegate is that you can reference local variables that are not passed as parameters. For example, the following sample creates a delegate that has no parameters. It then creates a local variable and an anonymous method that uses the variable.
delegate void MyAnonymousMethod();
static void Main(string[] args)
{
string myLocalString = "Hello World";
//Create an anonymous method using the local variable.
MyAnonymousMethod forward = delegate()
{
Console.WriteLine(string.Format("This is my string: {0}", myLocalString));
};
forward();
}
This method produces the same output as the previous method. As you can see, programmatically these methods produce the same output, but anonymous methods involve less coding.
Lambda expressions enable you to create an anonymous function using shorthand syntax. Consider the following:
delegate double square(double x);
static void Main(string[] args)
{
square myLambdaExpression = x => x * x;
Console.WriteLine("X squared is {0}", myLambdaExpression(5));
}
The lambda expression is x => x * x. When reading the code, you would say x goes to x times x. The => is called the goes to operator. The left side of the goes to operator evaluates to the input parameters of your method. The body of your method goes on the right side of the goes to operator. In this instance the method can square whatever number is passed into the method. If you need to pass multiple parameters, you use the following syntax:
delegate bool GreaterThan(double x, double y);
static void Main(string[] args)
{
GreaterThan gt = (x, y) => x > y;
Console.WriteLine("Is 6 greater than 5. {0}", gt(6, 5));
}
The preceding code produces the following output:
Is 6 greater than 5. True
When the method contains only a single expression, it is referred to as an expression lambda. When you need multiple statements in the body of the method it is referred to as a statement lambdas. Statement Lambdas are contained in brackets, {}. The following is a lambda expression for the WriteToConsoleBackward method:
s =>
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
Console.WriteLine("This is my string to write backwards: {0}",
new string(charArray));
};
You can also use a lambda expression to pass a function to a method. The following uses a lambda expression to call the WriteToConsole method:
WriteToConsole(x => Console.WriteLine("This is my string {0}", x), "Hello World");
As you can see, the syntax requires less typing as you go from delegate, to anonymous function, to lambda expressions.
This chapter described a number of topics, starting with reflection. Reflection is a powerful feature in the .NET Framework that enables you to examine all types within an assembly, create instances of classes, invoke methods, read attributes, and perform many other useful operations within your code.
Attributes are metadata that can be applied to a class, a method, or a property. You can use reflection to read attributes and dynamically change the behavior of your application.
You can create custom attribute classes and associate them with your own classes, properties, or methods. Custom attributes are created by creating a class that inherits from the System.Attribute class.
The Code Document Object Model (CodeDOM) is the object model provided by the .NET Framework that enables you to generate code dynamically in either C#, VB, or JScript. Learning the types in the System.CodeDOM namespace is useful when you want to automate tedious tasks or compile code programmatically.
Lambda expressions are shorthand syntax for creating anonymous functions. Anonymous functions are small methods without a signature. Lambda expressions are used extensively in LINQ.
Read each question carefully and select the answer or answers that represent the best solution to the problem. You can find the answers in Appendix A, “Answers to Chapter Test Questions.”
MyClass myClass = new MyClass();
MethodInfo myMethod = typeof(MyClass).GetMethod("Multiply");
Here are some additional useful resources to help you in your understanding of the topics presented in this chapter:
Complete list of Microsoft’s System.Reflection namespace classes http://msdn.microsoft.com/en-us/library/system.reflection(v=vs.110).aspx
Microsoft’s Attribute Reference http://msdn.microsoft.com/en-us/library/z0w1kczw(v=vs.110).aspx
Microsoft’s CodeDOM Quick Reference http://msdn.microsoft.com/en-us/library/f1dfsbhc.aspx
Microsoft’s Lambda Expression description http://msdn.microsoft.com/en-us/library/bb397687(v=vs.110).aspx
Code Project.com (a great reference site with a sample of reflection, CodeDOM, and lambda expressions) http://www.codeproject.com/
StackOverflow.com (a great reference site for sample code) http://www.StackOverflow.com
This cheat sheet is designed as a way for you to quickly study the key points of this chapter.
Reflection
Attributes
Code Document Object Model (CodeDOM)
Lambda expressions
anonymous method Enables you to associate a block of code with a delegate without declaring the method signature.
assembly A compiled piece of code in a DLL or EXE file.
attribute Enables you to associate metadata with assemblies, types, methods, properties, and so on.
Code Document Object Model (CodeDOM) Enables the developer to generate code in multiple languages at run time based on a single code set.
context When loading an assembly using reflection, the context is where reflection searches for the assembly.
contravariance Permits parameter types that are less derived than the delegate’s parameter types.
covariance Enables you to have a method with a more derived return type than the delegate’s return type.
delegate A type that references a method.
expression lambda A lambda expression that contains only one statement for the body.
Expression Tree Code in a tree-like structure where each node is an expression.
field A variable defined in a class or struct.
lambda expression Shorthand syntax for an anonymous method that can be associated with a delegate or expressions tree.
load context When loading an assembly using reflection, this context contains the assemblies found by probing.
load-from context When loading an assembly using reflection, this context contains the assemblies located in the pat passed into the LoadFrom method.
module A file that composes an assembly. Typically this is the DLL or EXE file.
probing The process of looking in the GAC, the host assembly store, the folder of the executing assembly, or the private bin folder of the executing assembly to find an assembly.
reflection Provides classes that can be used to read metadata or dynamically invoke behavior from a type.
reflection-only context When loading an assembly using reflection, this is the context that contains the assemblies loaded with the ReflectionOnlyLoad and ReflectionOnlyLoadFrom methods.
statement lambda A lambda expression with more than one statement in the body of the expression.
target The class, property, or method that contain metadata defined by an attribute.
type Any class, interface, array, value type, enumeration, parameter, generic type definition, and open or closed constructed generic type.
3.135.247.68