Visual CSharp 2005 Recipes (2006) [eng]
.pdf58C H A P T E R 2 ■ D ATA M A N I P U L AT I O N
//Remove the remaining items from the bag. string[] s = bag.RemoveAll();
//Wait to continue.
Console.WriteLine("\nMain method complete. Press Enter"); Console.ReadLine();
}
}
}
2-13. Store a Serializable Object to a File
Problem
You need to store a serializable object and its state to a file, and then deserialize it later.
Solution
Use a formatter to serialize the object and write it to a System.IO.FileStream object. When you need to retrieve the object, use the same type of formatter to read the serialized data from the file and deserialize the object. The .NET Framework class library includes the following formatter implementations for serializing objects to binary or SOAP format:
•System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
•System.Runtime.Serialization.Formatters.Soap.SoapFormatter
How It Works
Using the BinaryFormatter and SoapFormatter classes, you can serialize an instance of any serializable type. (See recipe 13-1 for details on how to make a type serializable.) The BinaryFormatter class produces a binary data stream representing the object and its state. The SoapFormatter class produces a SOAP document.
Both the BinaryFormatter and SoapFormatter classes implement the interface System.Runtime. Serialization.IFormatter, which defines two methods: Serialize and Deserialize. The Serialize method takes a System.IO.Stream reference and a System.Object reference as arguments, serializes the Object, and writes it to the Stream. The Deserialize method takes a Stream reference as an argument, reads the serialized object data from the Stream, and returns an Object reference to a deserialized object. You must cast the returned Object reference to the correct type.
■Caution To call the Serialize and Deserialize methods of the BinaryFormatter class, your code must be granted the SerializationFormatter element of the permission System.Security.Permissions. SecurityPermission. To call the Serialize and Deserialize methods of the SoapFormatter class, your code must be granted full trust, because the System.Runtime.Serialization.Formatters.Soap.dll assembly in which the SoapFormatter class is declared does not allow partially trusted callers. Refer to recipe 11-1 for more information about assemblies and partially trusted callers.
C H A P T E R 2 ■ D ATA M A N I P U L AT I O N |
59 |
The Code
The example shown here demonstrates the use of both BinaryFormatter and SoapFormatter to serialize a System.Collections.ArrayList object containing a list of people to a file. The ArrayList object is then deserialized from the files and the contents displayed to the console.
using System; using System.IO;
using System.Collections;
using System.Runtime.Serialization.Formatters.Soap; using System.Runtime.Serialization.Formatters.Binary;
namespace Apress.VisualCSharpRecipes.Chapter02
{
class Recipe02_13
{
//Serialize an ArrayList object to a binary file. private static void BinarySerialize(ArrayList list)
{
using (FileStream str = File.Create("people.bin"))
{
BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(str, list);
}
}
//Deserialize an ArrayList object from a binary file. private static ArrayList BinaryDeserialize()
{
ArrayList people = null;
using (FileStream str = File.OpenRead("people.bin"))
{
BinaryFormatter bf = new BinaryFormatter(); people = (ArrayList)bf.Deserialize(str);
}
return people;
}
//Serialize an ArrayList object to a SOAP file. private static void SoapSerialize(ArrayList list)
{
using (FileStream str = File.Create("people.soap"))
{
SoapFormatter sf = new SoapFormatter(); sf.Serialize(str, list);
}
}
//Deserialize an ArrayList object from a SOAP file. private static ArrayList SoapDeserialize()
{
ArrayList people = null;
using (FileStream str = File.OpenRead("people.soap"))
{
SoapFormatter sf = new SoapFormatter();
60 C H A P T E R 2 ■ D ATA M A N I P U L AT I O N
people = (ArrayList)sf.Deserialize(str);
}
return people;
}
public static void Main()
{
//Create and configure the ArrayList to serialize ArrayList people = new ArrayList(); people.Add("Graeme");
people.Add("Lin");
people.Add("Andy");
//Serialize the list to a file in both binary and SOAP form. BinarySerialize(people);
SoapSerialize(people);
//Rebuild the lists of people from the binary and SOAP
//serializations and display them to the console.
ArrayList binaryPeople = BinaryDeserialize();
ArrayList soapPeople = SoapDeserialize();
Console.WriteLine("Binary people:"); foreach (string s in binaryPeople)
{
Console.WriteLine("\t" + s);
}
Console.WriteLine("\nSOAP people:"); foreach (string s in soapPeople)
{
Console.WriteLine("\t" + s);
}
// Wait to continue.
Console.WriteLine("\nMain method complete. Press Enter"); Console.ReadLine();
}
}
}
Usage
To illustrate the different results achieved using the BinaryFormatter and SoapFormatter classes, Figure 2-1 shows the contents of the people.bin file generated using the BinaryFormatter class, and Figure 2-2 shows the contents of the people.soap file generated using the SoapFormatter class.
C H A P T E R 2 ■ D ATA M A N I P U L AT I O N |
61 |
Figure 2-1. Contents of the people.bin file
Figure 2-2. Contents of the people.soap file
2-14. Read User Input from the Console
Problem
You want to read user input from the Windows console, either a line or character at a time.
Solution
Use the Read or ReadLine method of the System.Console class to read input when the user presses Enter. To read input without requiring the user to press Enter, use the Console.ReadKey method.
How It Works
The simplest way to read input from the console is to use the static Read or ReadLine methods of the Console class. These methods will both cause your application to block, waiting for the user to enter input and press Enter. In both instances, the user will see the input characters in the console. Once the user presses Enter, the Read method will return an int value representing the next character of input data, or –1 if no more data is available. The ReadLine method will return a string containing all the data entered, or an empty string if no data was entered.
62 C H A P T E R 2 ■ D ATA M A N I P U L AT I O N
.NET Framework 2.0 adds the ReadKey method to the Console class, which provides a way to read input from the console without waiting for the user to press Enter. The ReadKey method waits for the user to press a key and returns a System.ConsoleKeyInfo object to the caller. By passing true as an argument to an overload of the ReadKey method, you can also prevent the key pressed by the user from being echoed to the console.
The returned ConsoleKeyInfo object contains details about the key pressed. The details are accessible through the properties of the ConsoleKeyInfo class summarized in Table 2-5.
Table 2-5. Properties of the ConsoleKeyInfo Class
Property |
Description |
Key |
Gets a value of the System.ConsoleKey enumeration representing the key |
|
pressed. The ConsoleKey enumeration contains values that represent all of the |
|
keys usually found on a keyboard. These include all the character and function |
|
keys; navigation and editing keys like Home, Insert, and Delete; and more modern |
|
specialized keys like the Windows key, media player control keys, browser |
|
activation keys, and browser navigation keys. |
KeyChar |
Gets a char value containing the Unicode character representation of the key |
|
pressed. |
Modifiers |
Gets a bitwise combination of values from the System.ConsoleModifiers |
|
enumeration that identifies one or more modifier keys pressed simultaneously |
|
with the console key. The members of the ConsoleModifiers enumeration are |
|
Alt, Control, and Shift. |
|
|
The KeyAvailable method of the Console class returns a bool value indicating whether input is available in the input buffer without blocking your code.
The Code
The following example reads input from the console one character at a time using the ReadKey method. If the user presses F1, the program toggles in and out of “secret” mode, where input is masked by asterisks. When the user presses Escape, the console is cleared and the input the user has entered is displayed. If the user presses Alt-X or Alt-x, the example terminates.
using System;
using System.Collections.Generic;
namespace Apress.VisualCSharpRecipes.Chapter02
{
class Recipe02_14
{
public static void Main()
{
//Local variable to hold the key entered by the user. ConsoleKeyInfo key;
//Control whether character or asterisk is displayed. bool secret = false;
//Character List for the user data entered. List<char> input = new List<char>();
C H A P T E R 2 ■ D ATA M A N I P U L AT I O N |
63 |
string msg = "Enter characters and press Escape to see input." + "\nPress F1 to enter/exit Secret mode and Alt-X to exit.";
Console.WriteLine(msg);
// Process input until the user enters "Alt-X" or "Alt-x". do
{
//Read a key from the console. Intercept the key so that it is not
//displayed to the console. What is displayed is determined later
//depending on whether the program is in secret mode.
key = Console.ReadKey(true);
// Switch secret mode on and off. if (key.Key == ConsoleKey.F1)
{
if (secret)
{
// Switch secret mode off. secret = false;
}
else
{
// Switch secret mode on. secret = true;
}
}
// Handle Backspace.
if (key.Key == ConsoleKey.Backspace)
{
if (input.Count > 0)
{
// Backspace pressed, remove the last character. input.RemoveAt(input.Count - 1);
Console.Write(key.KeyChar); Console.Write(" "); Console.Write(key.KeyChar);
}
}
// Handle Escape.
else if (key.Key == ConsoleKey.Escape)
{
Console.Clear(); Console.WriteLine("Input: {0}\n\n",
new String(input.ToArray())); Console.WriteLine(msg); input.Clear();
}
// Handle character input.
else if (key.Key >= ConsoleKey.A && key.Key <= ConsoleKey.Z)
{
input.Add(key.KeyChar);
64 C H A P T E R 2 ■ D ATA M A N I P U L AT I O N
if (secret)
{
Console.Write("*");
}
else
{
Console.Write(key.KeyChar);
}
}
}while (key.Key != ConsoleKey.X
||key.Modifiers != ConsoleModifiers.Alt);
// Wait to continue.
Console.WriteLine("\n\nMain method complete. Press Enter"); Console.ReadLine();
}
}
}
C H A P T E R 3
■ ■ ■
Application Domains, Reflection,
and Metadata
The power and flexibility of the Microsoft .NET Framework is enhanced by the ability to inspect and manipulate types and metadata at runtime. The recipes in this chapter describe how to use application domains, reflection, and metadata. Specifically, the recipes in this chapter describe how to do the following:
•Create application domains into which you can load assemblies that are isolated from the rest of your application (recipe 3-1)
•Create types that have the capability to cross application domain boundaries (recipe 3-2) and types that are guaranteed to be unable to cross application domain boundaries (recipe 3-4)
•Control the loading of assemblies and the instantiation of types in local and remote application domains (recipes 3-3, 3-5, 3-6, and 3-7)
•Pass simple configuration data between application domains (recipe 3-8)
•Unload application domains, which provides the only means through which you can unload assemblies at runtime (recipe 3-9)
•Inspect and test the type of an object using a variety of mechanisms built into the C# language and capabilities provided by the objects themselves (recipes 3-10 and 3-11)
•Dynamically instantiate an object and execute its methods at runtime using reflection (recipe 3-12)
•Create custom attributes (recipe 3-13), allowing you to associate metadata with your program elements and inspect the value of those custom attributes at runtime (recipe 3-14)
■Note An excellent reference for detailed information on all aspects of application domains and loading assemblies is Customizing the Microsoft .NET Framework Common Language Runtime by Steven Pratschner (Microsoft Press, 2005).
65
66 C H A P T E R 3 ■ A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA
3-1. Create an Application Domain
Problem
You need to create a new application domain.
Solution
Use the static method CreateDomain of the System.AppDomain class.
How It Works
The simplest overload of the CreateDomain method takes a single string argument specifying a humanreadable name (friendly name) for the new application domain. Other overloads allow you to specify evidence and configuration settings for the new application domain. You specify evidence using a System.Security.Policy.Evidence object, and you specify configuration settings using
a System.AppDomainSetup object.
The AppDomainSetup class is a container of configuration information for an application domain. Table 3-1 lists some of the properties of the AppDomainSetup class that you will use most often when creating application domains. These properties are accessible after creation through members of the AppDomain object. Some have different names, and some are modifiable at runtime; refer to the
.NET Framework’s software development kit (SDK) documentation on the AppDomain class for a comprehensive discussion.
Table 3-1. Commonly Used AppDomainSetup Properties
Property |
Description |
ApplicationBase |
The directory where the CLR will look during probing to resolve private |
|
assemblies. (Recipe 3-5 discusses probing.) Effectively, ApplicationBase |
|
is the root directory for the executing application. By default, this is the |
|
directory containing the assembly. This is readable after creation using |
|
the AppDomain.BaseDirectory property. |
ConfigurationFile |
The name of the configuration file used by code loaded into the |
|
application domain. This is readable after creation using the |
|
AppDomain.GetData method with the key APP_CONFIG_FILE. By |
|
default, the configuration file is stored in the same folder as the |
|
application .exe file, but if you set ApplicationBase, it will be in that |
|
same folder. |
DisallowPublisherPolicy |
Controls whether the publisher policy section of the application |
|
configuration file is taken into consideration when determining which |
|
version of a strong-named assembly to bind to. Recipe 3-5 discusses |
|
publisher policy. |
PrivateBinPath |
A semicolon-separated list of directories that the runtime uses when |
|
probing for private assemblies. These directories are relative to the |
|
directory specified in ApplicationBase. This is readable after application |
|
domain creation using the AppDomain.RelativeSearchPath property. |
|
|
The Code
The following code demonstrates the creation and initial configuration of an application domain:
C H A P T E R 3 ■ A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA |
67 |
using System;
namespace Apress.VisualCSharpRecipes.Chapter03
{
class Recipe03_01
{
public static void Main()
{
//Instantiate an AppDomainSetup object. AppDomainSetup setupInfo = new AppDomainSetup();
//Configure the application domain setup information. setupInfo.ApplicationBase = @"C:\MyRootDirectory"; setupInfo.ConfigurationFile = "MyApp.config"; setupInfo.PrivateBinPath = "bin;plugins;external";
//Create a new application domain passing null as the evidence
//argument. Remember to save a reference to the new AppDomain as
//this cannot be retrieved any other way.
AppDomain newDomain =
AppDomain.CreateDomain("My New AppDomain",null, setupInfo);
// Wait to continue.
Console.WriteLine("\nMain method complete. Press Enter."); Console.ReadLine();
}
}
}
■Note You must maintain a reference to the AppDomain object when you create it because no mechanism exists to enumerate existing application domains from within managed code.
3-2. Create Types That Can Be Passed Across Application Domain Boundaries
Problem
You need to pass objects across application domain boundaries as arguments or return values.
Solution
Use marshal-by-value or marshal-by-reference objects.
How It Works
The .NET Remoting system (discussed in Chapter 10) makes passing objects across application domain boundaries straightforward. However, to those unfamiliar with .NET Remoting, the results can be very different from those expected. In fact, the most confusing aspect of using multiple