Tải bản đầy đủ (.pdf) (98 trang)

Addison Wesley Essential C Sharp_7 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.88 MB, 98 trang )

ptg
Iterators 639
CSharpPrimitiveTypes primitives =
new CSharpPrimitiveTypes();
foreach (string primitive in primitives)
{
Console.WriteLine(primitive);
}
}
}
The results of Listing 16.13 appear in Output 16.5.
The output from this listing is a listing of the C# primitive types.
1
Iterators and State
When an iterator is first called in a foreach statement (such as foreach
(string primitive in primitives) in Listing 16.13), its state is initialized
within the enumerator. The iterator maintains its state as long as the
foreach statement at the call site continues to execute. When you yield a
value, process it, and resume the foreach statement at the call site, the iter-
ator continues where it left off the previous time around the loop and
OUTPUT 16.5:
object
byte
uint
ulong
float
char
bool
ushort
decimal
int


sbyte
short
long
void
double
string
1. In alpha versions of the C# 2.0 compiler, yield was a keyword rather than a contextual
keyword. However, such a change could result in an incompatibility between C# 1.0 and
C# 2.0. Instead, yield became a contextual keyword that must appear before return. As a
result, no code-breaking change occurred because C# 1.0 did not allow any text (besides
comments) prior to the return keyword.
From the Library of Wow! eBook
ptg
Chapter 16: Building Custom Collections640
continues processing. When the foreach statement at the call site termi-
nates, the iterator’s state is no longer saved. It is always safe to call the iter-
ator again since the generated code never resets the state of the iterator but
instead creates a new one when needed.
Figure 16.8 shows a high-level sequence diagram of what takes place.
Remember that the MoveNext() method appears on the IEnumerator<T>
interface.
Figure 16.8: Sequence Diagram with yield return

Program
primitives:
CSharpPrimitiveTypes
GetEnumerator()
enumerator:
Enumerator
Instantiate

yield return "object"
yield return "byte"
MoveNext()
yield return "string"
MoveNext()
Console
WriteLine()
WriteLine()
WriteLine()
MoveNext()
From the Library of Wow! eBook
ptg
Iterators 641
In Listing 16.13, the foreach statement at the call site initiates a call to
GetEnumerator() on the CSharpPrimitiveTypes instance called primi-
tives. Given the iterator instance (referenced by iterator), foreach begins
each iteration with a call to MoveNext(). Within the iterator, you yield a
value back to the foreach statement at the call site. After the yield return
statement, the GetEnumerator() method seemingly pauses until the next
MoveNext() request. Back at the call site, the foreach statement displays the
yielded value on the screen. It then loops back around and calls MoveNext()
on the iterator again. Notice that the second time, processing picks up at the
second yield return statement. Once again, the foreach displays on the
screen what CSharpPrimitiveTypes yielded and starts the loop again. This
process continues until there are no more yield return statements within
the iterator. At that point, the foreach loop at the call site terminates.
More Iterator Examples
Before you modify BinaryTree<T>, you must modify Pair<T> to support
the IEnumerable<T> interface using an iterator. Listing 16.14 is an example
that yields each element in Pair<T>.

Listing 16.14: Using yield to Implement BinaryTree<T>
public struct Pair<T>: IPair<T>,
{
public Pair(T first, T second)
{
_first = first;
_second = second;
}
public T First
{
get{ return _first; }
private set{ _first = value; }
}
private T _first;
public T Second
{
get{ return _second; }
private set{ _second = value; }
}
IEnumerable<T>
From the Library of Wow! eBook
ptg
Chapter 16: Building Custom Collections642
private T _second;
}
In Listing 16.14, the iteration over the Pair<T> data type loops twice: first
through yield return First, and then through yield return Second.
Each time the yield return statement within GetEnumerator() is encoun-
tered, the state is saved and execution appears to “jump” out of the
GetEnumerator() method context and into the context of the call site.

When the second iteration starts, GetEnumerator() begins to execute again
with the yield return Second statement.
System.Collections.Generic.IEnumerable<T> inherits from System.
Collections.IEnumerable. Therefore, when implementing IEnumera-
ble<T>, it is also necessary to implement IEnumerable. In Listing 16.14, you
do so explicitly, and the implementation simply involves a call to IEnumera-
ble<T>’s GetEnumerator() implementation. This call from IEnumerable.
GetEnumerator() to IEnumerable<T>.GetEnumerator() will always work
because of the type compatibility (via inheritance) between IEnumerable<T>
and IEnumerable. Since the signatures for both GetEnumerator()s are
identical (the return type does not distinguish a signature), one or both
implementations must be explicit. Given the additional type safety offered
by
IEnumerable<T>’s version, you implement IEnumerable’s implementa-
tion explicitly.
Listing 16.15 uses the Pair<T>.GetEnumerator() method and displays
"Inigo" and "Montoya" on two consecutive lines.
#region IEnumerable<T>
public IEnumerator<T> GetEnumerator()
{
yield return First;
yield return Second;
}
#endregion IEnumerable<T>
#region IEnumerable Members
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator
{
return GetEnumerator();
}

#endregion
From the Library of Wow! eBook
ptg
Iterators 643
Listing 16.15: Using Pair<T>.GetEnumerator() via foreach
Pair<string> fullname = new Pair<string>("Inigo", "Montoya");
foreach (string name in fullname)
{
Console.WriteLine(name);
}
Notice that the call to GetEnumerator() is implicit within the foreach loop.
Placing a yield return within a Loop
It is not necessary to hardcode each yield return statement, as you did in
both CSharpPrimitiveTypes and Pair<T>. Using the yield return state-
ment, you can return values from inside a loop construct. Listing 16.16
uses a foreach loop. Each time the foreach within GetEnumerator() exe-
cutes, it returns the next value.
Listing 16.16: Placing yield return Statements within a Loop
public class BinaryTree<T>: IEnumerable<T>
{
//
#region IEnumerable<T>
public IEnumerator<T> GetEnumerator()
{
// Return the item at this node.
yield return Value;

// Iterate through each of the elements in the pair.
}
#endregion IEnumerable<T>

foreach (BinaryTree<T> tree in SubItems)
{
if (tree != null)
{
// Since each element in the pair is a tree,
// traverse the tree and yield each
// element.
foreach (T item in tree)
{
yield return item;
}
}
}
From the Library of Wow! eBook
ptg
Chapter 16: Building Custom Collections644
#region IEnumerable Members
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
In Listing 16.16, the first iteration returns the root element within the
binary tree. During the second iteration you traverse the pair of subele-
ments. If the subelement pair contains a non-null value, then you traverse
into that child node and yield its elements. Note that foreach(T item in
tree) is a recursive call to a child node.
As observed with CSharpPrimitiveTypes and Pair<T>, you can now

iterate over BinaryTree<T> using a foreach loop. Listing 16.17 demon-
strates this, and Output 16.6 shows the results.
Listing 16.17: Using foreach with BinaryTree<string>
// JFK
jfkFamilyTree = new BinaryTree<string>(
"John Fitzgerald Kennedy");
jfkFamilyTree.SubItems = new Pair<BinaryTree<string>>(
new BinaryTree<string>("Joseph Patrick Kennedy"),
new BinaryTree<string>("Rose Elizabeth Fitzgerald"));
// Grandparents (Father's side)
jfkFamilyTree.SubItems.First.SubItems =
new Pair<BinaryTree<string>>(
new BinaryTree<string>("Patrick Joseph Kennedy"),
new BinaryTree<string>("Mary Augusta Hickey"));
// Grandparents (Mother's side)
jfkFamilyTree.SubItems.Second.SubItems =
new Pair<BinaryTree<string>>(
new BinaryTree<string>("John Francis Fitzgerald"),
new BinaryTree<string>("Mary Josephine Hannon"));
foreach (string name in jfkFamilyTree)
{
Console.WriteLine(name);
}
From the Library of Wow! eBook
ptg
Iterators 645
BEGINNER TOPIC
struct versus class
An interesting side effect of defining Pair<T> as a struct rather than a
class is that SubItems.First and SubItems.Second cannot be assigned

directly. The following will produce a compile error indicating that Sub-
Items cannot be modified, “because it is not a variable”:
jfkFamilyTree.SubItems.First =
new BinaryTree<string>("Joseph Patrick Kennedy");
The issue is that SubItems is a property of type Pair<T>, a struct. There-
fore, when the property returns the value, a copy of _SubItems is made,
and assigning First on a copy that is promptly lost at the end of the state-
ment would be misleading. Fortunately, the C# compiler prevents this.
To overcome the issue, don’t assign it (see the approach in Listing
16.17), use class rather than struct for Pair<T>, don’t create a SubItems
property and instead use a field, or provide properties in BinaryTree<T>
that give direct access to _SubItems members.
Canceling Further Iteration: yield break
Sometimes you might want to cancel further iteration. You can do this by
including an if statement so that no further statements within the code
are executed. However, you can also jump back to the call site, causing
MoveNext() to return false. Listing 16.18 shows an example of such a
method.
OUTPUT 16.6:
John Fitzgerald Kennedy
Joseph Patrick Kennedy
Patrick Joseph Kennedy
Mary Augusta Hickey
Rose Elizabeth Fitzgerald
John Francis Fitzgerald
Mary Josephine Hannon
From the Library of Wow! eBook
ptg
Chapter 16: Building Custom Collections646
Listing 16.18: Escaping Iteration via yield break

public System.Collections.Generic.IEnumerable<T>
GetNotNullEnumerator()
{
yield return Second;
yield return First;
}
This method cancels the iteration if either of the elements in the Pair<T>
class is null.
A yield break statement is similar to placing a return statement at the
top of a function when it is determined that there is no work to do. It is a way
to exit from further iterations without surrounding all remaining code with
an if block. As such, it allows multiple exits, and therefore, you should use
it with caution because casual reading of the code may miss the early exit.
ADVANCED TOPIC
How Iterators Work
When the C# compiler encounters an iterator, it expands the code into the
appropriate CIL for the corresponding enumerator design pattern. In the
generated code, the C# compiler first creates a nested private class to imple-
ment the IEnumerator<T> interface, along with its Current property and a
MoveNext() method. The Current property returns a type corresponding to
the return type of the iterator. Listing 16.14 of Pair<T> contains an iterator
that returns a T type. The C# compiler examines the code contained within
the iterator and creates the necessary code within the MoveNext method and
the Current property to mimic its behavior. For the Pair<T> iterator, the C#
compiler generates roughly equivalent code (see Listing 16.19).
Listing 16.19: C# Equivalent of Compiler-Generated C# Code for Iterators
using System;
using System.Collections.Generic;
if((First == null) || (Second == null))
{

yield break;
}
From the Library of Wow! eBook
ptg
Iterators 647
public class Pair<T> : IPair<T>, IEnumerable<T>
{
//
// The iterator is expanded into the following
// code by the compiler
public virtual IEnumerator<T> GetEnumerator()
{
__ListEnumerator result = new __ListEnumerator(0);
result._Pair = this;
return result;
}
public virtual System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return new GetEnumerator();
}
private sealed class __ListEnumerator<T> : IEnumerator<T>
{
public __ListEnumerator(int itemCount)
{
_ItemCount = itemCount;
}
Pair<T> _Pair;
T _Current;
int _ItemCount;

public object Current
{
get
{
return _Current;
}
}
public bool MoveNext()
{
switch (_ItemCount)
{
case 0:
_Current = _Pair.First;
_ItemCount++;
return true;
case 1:
_Current = _Pair.Second;
_ItemCount++;
From the Library of Wow! eBook
ptg
Chapter 16: Building Custom Collections648
return true;
default:
return false;
}
}
}
}
Because the compiler takes the yield return statement and generates cla-
sses that correspond to what you probably would have written manually,

iterators in C# exhibit the same performance characteristics as classes
that implement the enumerator design pattern manually. Although there
is no performance improvement, the programmer productivity gained is
significant.
Creating Multiple Iterators in a Single Class
Previous iterator examples implemented IEnumerable<T>.GetEnumera-
tor(). This is the method that foreach seeks implicitly. Sometimes you
might want different iteration sequences, such as iterating in reverse, fil-
tering the results, or iterating over an object projection other than the
default. You can declare additional iterators in the class by encapsulating
them within properties or methods that return IEnumerable<T> or IEnu-
merable. If you want to iterate over the elements of Pair<T> in reverse, for
example, you provide a GetReverseEnumerator() method, as shown in
Listing 16.20.
Listing 16.20: Using yield return in a Method That Returns IEnumerable<T>
public struct Pair<T>: IEnumerable<T>
{

{
yield return Second;
yield return First;
}

}
public void Main()
{
Pair<string> game = new Pair<string>("Redskins", "Eagles");
public IEnumerable<T> GetReverseEnumerator()
From the Library of Wow! eBook
ptg

Iterators 649
{
Console.WriteLine(name);
}
}
Note that you return IEnumerable<T>, not IEnumerator<T>. This is differ-
ent from IEnumerable<T>.GetEnumerator(), which returns IEnumerator<T>.
The code in Main() demonstrates how to call GetReverseEnumerator() using
a foreach loop.
yield Statement Characteristics
You can declare the yield return statement only in members that return
an IEnumerator<T> or IEnumerable<T> type, or their nongeneric equiva-
lents. More specifically, you can use yield only in GetEnumerator() meth-
ods that return IEnumerator<T>, or in methods that return IEnumerable<T>
but are not called GetEnumerator().
Methods that include a yield return statement may not have a simple
return. If the method uses the yield return statement, then the C# com-
piler generates the necessary code to maintain the state machine for the
iterator. In contrast, if the method uses the return statement instead of
yield return, the programmer is responsible for maintaining his own
state machine and returning an instance of one of the iterator interfaces.
Further, just as all code paths in a method with a return type must contain
a return statement accompanied by a value (assuming they don’t throw an
exception), all code paths in an iterator must contain a yield return state-
ment if they are to return any data.
Additional restrictions on the yield statement that result in compiler
errors are as follows.
• The yield statement may not appear outside a method, operator, or
property accessor.
• The yield statement may not appear in an anonymous method (see

Chapter 12).
• The yield statement may not appear inside the catch and finally
clauses of the try statement. Furthermore, a yield statement may
appear in a try block only if there is no catch block.
foreach (string name in game.GetReverseEnumerator())
From the Library of Wow! eBook
ptg
Chapter 16: Building Custom Collections650
SUMMARY
The generic collection classes and interfaces made available in C# 2.0 are
universally superior to their nongeneric counterparts; by avoiding boxing
penalties and enforcing type rules at compile time, they are faster and
safer. Unless you are limited to C# 1.0, you should consider the entire
namespace of System.Collections as obsolete (in fact, it has been
excluded from the Silverlight CLR entirely). In other words, don’t go back
and necessarily remove all code that already uses this namespace. Instead,
use System.Collections.Generics for any new code and, over time, con-
sider migrating existing code to use the corresponding generic collections
which contain both the interfaces and the classes for working with collec-
tions of objects.
Providing the System.Collections.Generic namespace is not the only
change that C# 2.0 brought to collections. Another significant addition is
the iterator. Iterators involve a new contextual keyword, yield, that C#
uses to generate underlying CIL code that implements the iterator pattern
used by the foreach loop.
From the Library of Wow! eBook
ptg
651
17
Reflection, Attributes, and

Dynamic Programming
TTRIBUTES ARE A MEANS of inserting additional metadata into an
assembly and associating the metadata with a programming con-
struct such as a class, method, or property. This chapter investigates the
details surrounding attributes that are built into the framework, as well as
how to define custom attributes. In order to take advantage of custom
attributes, it is necessary to identify them. This is handled through reflec-
tion. This chapter begins with a look at reflection, including how you can
use it to dynamically bind at runtime and call a member using its name at
compile time. This is frequently performed within tools such as a code gen-
erator. In addition, reflection is used at execution time when the call target
is unknown.
A
2
4
5
6
7
1
8
Accessing
Metadata
GetType()
typeof()
Member Invocation
3
Reflection on Generics
Custom Attributes
Attribute Constructors
Named Parameters

Predefined
Attributes
Dynamic Programming
Reflection,
Attributes, and
Dynamic
Programming
AttributeUsageAttribute
ConditionalAttribute
ObsoleteAttribute
Serialization
From the Library of Wow! eBook
ptg
Chapter 17: Reflection, Attributes, and Dynamic Programming 652
The chapter ends with a discussion of dynamic programming, a feature
added in C# 4.0 that greatly simplifies working with data that is dynamic
and requires execution-time rather than compile-time binding.
Reflection
Using reflection, it is possible to do the following:
• Access the metadata for types within an assembly. This includes con-
structs such as the full type name, member names, and any attributes
decorating the construct.
• Dynamically invoke a type’s member at runtime using the metadata,
rather than a compile-time-defined binding.
Reflection is the process of examining the metadata within an assem-
bly. Traditionally, when code compiles down to a machine language, all
the metadata (such as type and method names) about the code is dis-
carded. In contrast, when C# compiles into the CIL, it maintains most of
the metadata about the code. Furthermore, using reflection, it is possible to
enumerate through all the types within an assembly and search for those

that match certain criteria. You access a type’s metadata through instances
of
System.Type, and this object includes methods for enumerating the type
instance’s members. Furthermore, it is possible to invoke those members
on particular objects that are of the examined type.
The facility for reflection enables a host of new paradigms that other-
wise are unavailable. For example, reflection enables you to enumerate
over all the types within an assembly, along with their members, and in the
process create stubs for documentation of the assembly API. You can then
combine the metadata retrieved from reflection with the XML document
created from XML comments (using the/doc switch) to create the API doc-
umentation. Similarly, programmers use reflection metadata to generate
code for persisting (serializing) business objects into a database. It could
also be used in a list control that displays a collection of objects. Given the
collection, a list control could use reflection to iterate over all the proper-
ties of an object in the collection, defining a column within the list for each
From the Library of Wow! eBook
ptg
Reflection 653
property. Furthermore, by invoking each property on each object, the list
control could populate each row and column with the data contained
in the object, even though the data type of the object is unknown at
compile time.
XmlSerializer, ValueType, and DataBinder are a few of the classes in
the framework that use reflection for portions of their implementation
as well.
Accessing Metadata Using System.Type
The key to reading a type’s metadata is to obtain an instance of
System.Type that represents the target type instance. System.Type pro-
vides all the methods for retrieving the information about a type. You can

use it to answer questions such as the following.
• What is the type’s name (Type.Name)?
• Is the type public (Type.IsPublic)?
• What is the type’s base type (Type.BaseType)?
• Does the type support any interfaces (Type.GetInterfaces())?
• Which assembly is the type defined in (Type.Assembly)?
• What are a type’s properties, methods, fields, and so on (Type.Get-
Properties(), Type.GetMethods(), Type.GetFields(), and so on)?
• What attributes decorate a type (Type.GetCustomAttributes())?
There are more such members, but in summary, they all provide infor-
mation about a particular type. The key is to obtain a reference to a type’s
Type object, and the two primary ways to do this are through object.
GetType() and typeof().
Note that the GetMethods() call does not return extension methods.
They are available only as static members on the implementing type.
GetType()
object includes a GetType() member, and therefore, all types include this
function. You call GetType() to retrieve an instance of System.Type corre-
sponding to the original object. Listing 17.1 demonstrates this, using a Type
instance from DateTime. Output 17.1 shows the results.
From the Library of Wow! eBook
ptg
Chapter 17: Reflection, Attributes, and Dynamic Programming 654
Listing 17.1: Using Type.GetProperties() to Obtain an Object’s Public Properties
DateTime dateTime = new DateTime();
Type type = dateTime.GetType();
foreach (
System.Reflection.PropertyInfo property in
type.GetProperties())
{

Console.WriteLine(property.Name);
}
After calling GetType(), you iterate over each System.Reflection.
PropertyInfo instance returned from Type.GetProperties() and display
the property names. The key to calling GetType() is that you must have an
object instance. However, sometimes no such instance is available. Static
classes, for example, cannot be instantiated, so there is no way to call
GetType().
typeof()
Another way to retrieve a Type object is with the typeof expression. typeof
binds at compile time to a particular Type instance, and it takes a type
directly as a parameter. Listing 17.2 demonstrates the use of typeof with
Enum.Parse().
OUTPUT 17.1:
Date
Day
DayOfWeek
DayOfYear
Hour
Kind
Millisecond
Minute
Month
Now
UtcNow
Second
Ticks
TimeOfDay
Today
Year

From the Library of Wow! eBook
ptg
Reflection 655
Listing 17.2: Using typeof() to Create a System.Type Instance
using System.Diagnostics;
//
ThreadPriorityLevel priority;
priority = (ThreadPriorityLevel)Enum.Parse(
typeof(ThreadPriorityLevel), "Idle");
//
Enum.Parse() takes a Type object identifying an enum and then converts
a string to the specific enum value. In this case, it converts "Idle" to
System.Diagnostics.ThreadPriorityLevel.Idle.
Member Invocation
The possibilities with reflection don’t stop with retrieving the metadata.
The next step is to take the metadata and dynamically invoke the members
it references. Consider the possibility of defining a class to represent an
application’s command line. The difficulty with a CommandLineInfo class
such as this has to do with populating the class with the actual command-
line data that started the application. However, using reflection, you can
map the command-line options to property names and then dynamically
set the properties at runtime. Listing 17.3 demonstrates this example.
Listing 17.3: Dynamically Invoking a Member
using System;
using System.Diagnostics;
public partial class Program
{
public static void Main(string[] args)
{
string errorMessage;

CommandLineInfo commandLine = new CommandLineInfo();
if (!CommandLineHandler.TryParse(
args, commandLine, out errorMessage))
{
Console.WriteLine(errorMessage);
DisplayHelp();
}
if (commandLine.Help)
{
From the Library of Wow! eBook
ptg
Chapter 17: Reflection, Attributes, and Dynamic Programming 656
DisplayHelp();
}
else
{
if (commandLine.Priority !=
ProcessPriorityClass.Normal)
{
// Change thread priority
}
}
//

}
private static void DisplayHelp()
{
// Display the command-line help.
}
}

using System;
using System.Diagnostics;
public partial class Program
{
private class CommandLineInfo
{
public bool Help { get; set; }
public string Out { get; set; }
public ProcessPriorityClass Priority
{
get { return _Priority; }
set { _Priority = value; }
}
private ProcessPriorityClass _Priority =
ProcessPriorityClass.Normal;
}
}
using System;
using System.Diagnostics;
using System.Reflection;
public class CommandLineHandler
{
From the Library of Wow! eBook
ptg
Reflection 657
public static void Parse(string[] args, object commandLine)
{
string errorMessage;
if (!TryParse(args, commandLine, out errorMessage))
{

throw new ApplicationException(errorMessage);
}
}
public static bool TryParse(string[] args, object commandLine,
out string errorMessage)
{
bool success = false;
errorMessage = null;
foreach (string arg in args)
{
string option;
if (arg[0] == '/' || arg[0] == '-')
{
string[] optionParts = arg.Split(
new char[] { ':' }, 2);
// Remove the slash|dash
option = optionParts[0].Remove(0, 1);
PropertyInfo property =
commandLine.GetType().GetProperty(option,
BindingFlags.IgnoreCase |
BindingFlags.Instance |
BindingFlags.Public);
if (property != null)
{
if (property.PropertyType == typeof(bool))
{
// Last parameters for handling indexers
property.SetValue(
commandLine, true, null);
success = true;

}
else if (
property.PropertyType == typeof(string))
{
property.SetValue(
commandLine, optionParts[1], null);
success = true;
}
else if (property.PropertyType.IsEnum)
{
try
{
property.SetValue(commandLine,
Enum.Parse(
From the Library of Wow! eBook
ptg
Chapter 17: Reflection, Attributes, and Dynamic Programming 658
catch (ArgumentException )
{
success = false;
errorMessage =
string.Format(
"The option '{0}' is " +
"invalid for '{1}'",
optionParts[1], option);
}
}
else
{
success = false;

errorMessage = string.Format(
"Data type '{0}' on {1} is not"
+ " supported.",
property.PropertyType.ToString(),
commandLine.GetType().ToString());
}
}
else
{
success = false;
errorMessage = string.Format(
"Option '{0}' is not supported.",
option);
}
}
}
return success;
}
}
Although Listing 17.3 is long, the code is relatively simple. Main()
begins by instantiating a CommandLineInfo class. This type is defined spe-
cifically to contain the command-line data for this program. Each property
corresponds to a command-line option for the program where the com-
mand line is as shown in Output 17.2.
typeof(ProcessPriorityClass),
optionParts[1], true),
null);
success = true;
}
OUTPUT 17.2:

Compress.exe /Out:<file name> /Help
/Priority:RealTime|High|AboveNormal|Normal|BelowNormal|Idle
From the Library of Wow! eBook
ptg
Reflection 659
The CommandLineInfo object is passed to the CommandLineHandler’s
TryParse() method. This method begins by enumerating through each
option and separating out the option name (Help or Out, for example).
Once the name is determined, the code reflects on the CommandLineInfo
object, looking for an instance property with the same name. If the prop-
erty is found, it assigns the property using a call to SetValue() and
specifies the data corresponding to the property type. (For arguments, this
call accepts the object on which to set the value, the new value, and an
additional
index parameter that is null unless the property is an indexer.)
This listing handles three property types: Boolean, string, and enum. In the
case of enums, you parse the option value and assign the property the
text’s enum equivalent. Assuming the
TryParse() call was successful, the
method exits and the CommandLineInfo object is initialized with the data
from the command line.
Interestingly, in spite of the fact that
CommandLineInfo is a private class
nested within Program, CommandLineHandler has no trouble reflecting over
it and even invoking its members. In other words, reflection is able to cir-
cumvent accessibility rules as long as appropriate code access security
(CAS; see chapter 21) permissions are established. If, for example,
Out was
private, it would still be possible for the TryParse() method to assign it a
value. Because of this, it would be possible to move CommandLineHandler

into a separate assembly and share it across multiple programs, each with
their own CommandLineInfo class.
In this particular example, you invoke a member on CommandLineInfo
using PropertyInfo.SetValue(). Not surprisingly, PropertyInfo also
includes a GetValue() method for retrieving data from the property. For a
method, however, there is a MethodInfo class with an Invoke() member.
Both MethodInfo and PropertyInfo derive from MemberInfo (although
indirectly), as shown in Figure 17.1.
The CAS permissions are set up to allow private member invocation in
this case because the program runs from the local computer, and by
default, locally installed programs are part of the trusted zone and have
appropriate permissions granted. Programs run from a remote location
will need to be explicitly granted such a right.
From the Library of Wow! eBook
ptg
Chapter 17: Reflection, Attributes, and Dynamic Programming 660
Reflection on Generic Types
Just as you can use reflection on nongeneric types, the 2.0 framework
included provisions for reflecting on generic types. Runtime reflection on
generics determines whether a class or method contains a generic type,
and any type parameters or arguments it may include.
Determining the Type of Type Parameters
In the same way that you can use a typeof operator with nongeneric types to
retrieve an instance of System.Type, you can use the typeof operator on type
parameters in a generic type or generic method. Listing 17.4 applies the
typeof operator to the type parameter in the Add method of a Stack class.
Figure 17.1: MemberInfo Derived Classes
From the Library of Wow! eBook
ptg
Reflection 661

Listing 17.4: Declaring the Stack<T> Class
public class Stack<T>
{

public void Add(T i)
{

Type t = typeof(T);

}

}
Once you have an instance of the Type object for the type parameter,
you may then use reflection on the type parameter itself to determine its
behavior and tailor the Add method to the specific type more effectively.
Determining Whether a Class or Method Supports Generics
In the System.Type class for CLI 2.0, a handful of methods were added to
determine whether a given type supports generic parameters and argu-
ments. A generic argument is a type parameter supplied when a generic
class is instantiated. You can determine whether a class or method con-
tains generic parameters that have not yet been set by querying the
Type.ContainsGenericParameters Boolean property, as demonstrated in
Listing 17.5.
Listing 17.5: Reflection with Generics
using System;
public class Program
{
static void Main()
{
Type type;

type = typeof(System.Nullable<>);
Console.WriteLine(type.ContainsGenericParameters);
Console.WriteLine(type.IsGenericType);
type = typeof(System.Nullable<DateTime>);
Console.WriteLine(!type.ContainsGenericParameters);
Console.WriteLine(type.IsGenericType);
}
}
From the Library of Wow! eBook
ptg
Chapter 17: Reflection, Attributes, and Dynamic Programming 662
Output 17.3 shows the results of Listing 17.5.
Type.IsGenericType is a Boolean property that evaluates whether a
type is generic.
Obtaining Type Parameters for a Generic Class or Method
You can obtain a list of generic arguments, or type parameters, from a generic
class by calling the GetGenericArguments() method. The result is an array of
System.Type instances that corresponds to the order in which they are
declared as type parameters of the generic class. Listing 17.6 reflects into a
generic type and obtains each type parameter. Output 17.4 shows the results.
Listing 17.6: Using Reflection with Generic Types
using System;
using System.Collections.Generic;
public partial class Program
{
public static void Main()
{
Stack<int> s = new Stack<int>();
Type t = s.GetType();
foreach(Type type in t.GetGenericArguments())

{
System.Console.WriteLine(
"Type parameter: " + type.FullName);
}
//
}
}
OUTPUT 17.3:
True
True
True
True
OUTPUT 17.4:
Type parameter: System.Int32
From the Library of Wow! eBook
ptg
Attributes 663
Attributes
Before delving into details on how to program attributes, you should
consider a use case that demonstrates their utility. In the CommandLine-
Handler example in Listing 17.3, you dynamically set a class’s properties
based on the command-line option matching the property name. This
approach is insufficient, however, when the command-line option is an
invalid property name. /?, for example, cannot be supported. Further-
more, this mechanism doesn’t provide any way of identifying which
options are required versus which are optional.
Instead of relying on an exact match between the option name and the
property name, attributes provide a way of identifying additional meta-
data about the decorated construct—in this case, the option that the attri-
bute decorates. With attributes, you can decorate a property as

Required
and provide a /? option alias. In other words, attributes are a means of
associating additional data with a property (and other constructs).
Attributes appear within square brackets preceding the construct they
decorate. For example, you can modify the CommandLineInfo class to
include attributes, as shown in Listing 17.7.
Listing 17.7: Decorating a Property with an Attribute
class CommandLineInfo
{
public bool Help
{
get { return _Help; }
set { _Help = value; }
}
private bool _Help;
public string Out
{
get { return _Out; }
set { _Out = value; }
}
private string _Out;
public System.Diagnostics.ProcessPriorityClass Priority
{
get { return _Priority; }
[CommandLineSwitchAlias("?")]
[CommandLineSwitchRequired]
From the Library of Wow! eBook

×