Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 59 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
59
Dung lượng
720,72 KB
Nội dung
Programming C#, 2nd Edition 408 When two assemblies have different major or minor numbers, they are considered to be incompatible. When they have different build numbers, they might or might not be compatible, and when they have different revision numbers, they are considered definitely compatible with each other. Revision numbers are intended for bug fixes. If you fix a bug and are prepared to certify that your DLL is fully backward-compatible with the existing version, you should increment the revision. When an application loads an assembly, it specifies the major and minor version that it wants, and the AssemblyResolverfinds the highest build and revision numbers. 17.8.3 Strong Names In order to use a shared assembly, you must meet three requirements: • You need to be able to specify the exact assembly you want to load. Therefore, you need a globally unique name for the shared assembly. • You need to ensure that the assembly has not been tampered with. That is, you need a digital signature for the assembly when it is built. • You need to ensure that the assembly you are loading is the one authored by the actual creator of the assembly. You therefore need to record the identity of the originator. All these requirements are met by strong names. Strong names must be globally unique and use public key encryption to ensure that the assembly hasn't been tampered with and was written by the creator. A strong name is a string of hexadecimal digits and is not meant to be human-readable. To create a strong name, a public-private key pair is generated for the assembly. A hash is taken of the names and contents of the files in the assembly. The hash is then encrypted with the private key for the assembly and placed in the manifest. This is known as signing the assembly. The public key is incorporated into the strong name of the assembly. Public Key Encryption Strong names are based on public key encryption technology. The essence of public key encryption is that your data is encoded with a complex mathematical formula that returns two keys. Data encrypted with the first key can only be decrypted with the second. Data encrypted with the second key can only be decrypted with the first. Distribute your first key as a public key that anyone can have. Keep your second key as a private key that no one but you can have access to. The reciprocal relationship between the keys allows anyone to encrypt data with your public key, and then you can decrypt it with your private key. No one else has access to the data once it is encrypted, including the person who encrypted it. Similarly, you can encrypt data with your private key, and then anyone can decrypt that data with your public key. Although this makes the data freely available, it ensures that only you could have created it. This is called a digital signature . Programming C#, 2nd Edition 409 When an application loads the assembly, the CLR uses the public key to decode the hash of the files in the assembly to ensure that they have not been tampered with. This also protects against name clashes. You can create a strong name with the sn utility: sn -k c:\myStrongName.snk The -k flag indicates that you want a new key pair written to the specified file. You can call the file anything you like. Remember, a strong name is a string of hexadecimal digits and is not meant to be human-readable. You can associate this strong name with your assembly by using an attribute: using System.Runtime.CompilerServices; [assembly: AssemblyKeyFile("c:\myStrongName.key")] Attributes are covered in detail in Chapter 8. For now, you can just put this code at the top of your file to associate the strong name you generated with your assembly. 17.8.4 The Global Assembly Cache Once you've created your strong name and associated it with your assembly, all that remains is to place the assembly in the GAC, which is a reserved system directory. You can do that with the gacutil utility: gacutil /i MySharedAssembly.dll Or you can open your File Explorer and drag your assembly into the GAC. To see the GAC, open the File Explorer and navigate to %SystemRoot%\assembly; Explorer turns into a GAC utility. 17.8.5 Building a Shared Assembly The best way to understand shared assemblies is to build one. Let's return to the earlier multi- module project (see Examples 17-1 through 17-4) and navigate to the directory that contains the files calc.cs and fraction.cs. Try this experiment: locate the bin directory for the driver program and make sure that you do not have a local copy of the MySharedAssembly DLL files. The referenced assembly (MySharedAssembly) should have its CopyLocal property set to false. Run the program. It should fail with an exception saying it cannot load the assembly: Programming C#, 2nd Edition 410 Unhandled Exception: System.IO.FileNotFoundException: File or assembly name MySharedAssembly, or one of its dependencies, was not found. File name: "MySharedAssembly" at Programming_CSharp.Test.UseCS( ) at Programming_CSharp.Test.Main( ) Now copy the DLLs into the driver program's directory tree, run it again, and this time you should find that it works fine. Let's make the MySharedAssembly into a shared assembly. This is done in two steps. First, create a strong name for the assembly, and then you put the assembly into the GAC. 17.8.5.1 Step 1: Create a strong name Create a key pair by opening a command window and entering: sn -k keyFile.snk Now open the AssemblyInfo.cs file in the project for the MySharedAssembly.dll and modify this line: [assembly: AssemblyKeyFile("")] as follows: [assembly: AssemblyKeyFile(".\\keyFile.snk")] This sets the key file for the assembly. Rebuild with the same make file as earlier, and then open the resulting DLL in ILDasm and open the manifest. You should see a public key, as shown in Figure 17-8. Figure 17-8. The originator in the manifest of MySharedAssembly.dll By adding the strong name, you have signed this assembly (your exact values will be different). You now need to get the strong name from the DLL. To do this, navigate to the directory with the DLL and enter the following at a command prompt: sn -T MySharedAssembly.dll Note that sn is case-sensitive. Do not write sn -t. The response should be something like this: Programming C#, 2nd Edition 411 Public key token is 01fad8e0f0941a4d This value is an abbreviated version of the assembly's public key, called the public key token . Remove the DLLs from the test program's directory structure and run it again. It should fail again. Although you've given this assembly a strong name, you've not yet registered it in the GAC. 17.8.5.2 Step 2: Put the shared assembly in the GAC The next step is to drag the library into the GAC. To do so, open an Explorer window and navigate to the %SystemRoot% directory. When you double-click the Assembly subdirectory, Explorer will turn into a GAC viewer. You can drag and drop into the GAC viewer, or you can invoke this command-line utility: Gacutil /i mySharedAssembly.dll In either case, be sure to check that your assembly was loaded into the GAC, and that the originator value shown in the GAC viewer matches the value you got back from sn: Public key token is 01fad8e0f0941a4d This is illustrated in Figure 17-9. Figure 17-9. The GAC Once this is done, you have a shared assembly that can be accessed by any client. Refresh the client by building it again and look at its manifest, as shown in Figure 17-10. Figure 17-10. The manifest There's MySharedAssembly, listed as an external assembly, and the public key now matches the value shown in the GAC. Very nice, time to try it. Close ILDasm and compile and run your code. It should work fine, even though there are no DLLs for this library in its immediate path. You have just created and used a shared assembly. Programming C#, 2nd Edition 412 Chapter 18. Attributes and Reflection Throughout this book, I have emphasized that a .NET application contains code, data, and metadata. Metadata is information about the data that is, information about the types, code, assembly, and so forth stored along with your program. This chapter explores how some of that metadata is created and used. Attributes are a mechanism for adding metadata, such as compiler instructions and other data about your data, methods, and classes, to the program itself. Attributes are inserted into the metadata and are visible through ILDasm and other metadata-reading tools. Reflection is the process by which a program can read its own metadata. A program is said to reflect on itself, extracting metadata from its assembly and using that metadata either to inform the user or to modify its own behavior. 18.1 Attributes An attribute is an object that represents data you want to associate with an element in your program. The element to which you attach an attribute is referred to as the target of that attribute. For example, the attribute: [NoIDispatch] is associated with a class or an interface to indicate that the target class should derive from IUnknown rather than IDispatch when exporting to COM. COM interface programming is discussed in detail in Chapter 22. In Chapter 17, you saw this attribute: [assembly: AssemblyKeyFile("c:\\myStrongName.key")] This inserts metadata into the assembly to designate the program's StrongName. 18.2 Intrinsic Attributes Attributes come in two flavors: intrinsic and custom . Intrinsic attributes are supplied as part of the Common Language Runtime (CLR), and they are integrated into .NET. Custom attributes are attributes you create for your own purposes. Most programmers will use only intrinsic attributes, though custom attributes can be a powerful tool when combined with reflection, described later in this chapter. 18.2.1 Attribute Targets If you search through the CLR, you'll find a great many attributes. Some attributes are applied to an assembly, others to a class or interface, and some, such as [WebMethod], are applied to class members. These are called the attribute targets. Possible attribute targets are detailed in Table 18-1. Programming C#, 2nd Edition 413 Table 18-1. Possible attribute targets Member name Usage All Applied to any of the following elements: assembly, class, constructor, delegate, enum, event, field, interface, method, module, parameter, property, return value, or struct Assembly Applied to the assembly itself Class Applied to instances of the class Constructor Applied to a given constructor Delegate Applied to the delegated method Enum Applied to an enumeration Event Applied to an event Field Applied to a field Interface Applied to an interface Method Applied to a method Module Applied to a single module Parameter Applied to a parameter of a method Property Applied to a property (both get and set, if implemented) ReturnValue Applied to a return value Struct Applied to a struct 18.2.2 Applying Attributes Apply attributes to their targets by placing them in square brackets immediately before the target item. You can combine attributes by stacking one on top of another: [assembly: AssemblyDelaySign(false)] [assembly: AssemblyKeyFile(".\\keyFile.snk")] This can also be done by separating the attributes with commas: [assembly: AssemblyDelaySign(false), assembly: AssemblyKeyFile(".\\keyFile.snk")] You must place assembly attributes after all using statements and before any code. Many intrinsic attributes are used for interoperating with COM, as discussed in detail in Chapter 22. You've already seen use of one attribute ([WebMethod]) in Chapter 16. You'll see other attributes, such as the [Serializable] attribute, used in the discussion of serialization in Chapter 19. The System.Runtime namespace offers a number of intrinsic attributes, including attributes for assemblies (such as the keyname attribute), for configuration (such as debug to indicate the debug build), and for version attributes. You can organize the intrinsic attributes by how they are used. The principal intrinsic attributes are those used for COM, those used to modify the Interface Definition Language (IDL) file from within a source-code file, those used by the ATL Server classes, and those used by the Visual C++ compiler. Programming C#, 2nd Edition 414 Perhaps the attribute you are most likely to use in your everyday C# programming (if you are not interacting with COM) is [Serializable]. As you'll see in Chapter 19, all you need to do to ensure that your class can be serialized to disk or to the Internet is add the [Serializable] attribute to the class: [Serializable] class MySerializableClass The attribute tag is put in square brackets immediately before its target in this case, the class declaration. The key fact about intrinsic attributes is that you know when you need them; the task will dictate their use. 18.3 Custom Attributes You are free to create your own custom attributes and use them at runtime as you see fit. Suppose, for example, that your development organization wants to keep track of bug fixes. You already keep a database of all your bugs, but you'd like to tie your bug reports to specific fixes in the code. You might add comments to your code along the lines of: // Bug 323 fixed by Jesse Liberty 1/1/2005. This would make it easy to see in your source code, but there is no enforced connection to Bug 323 in the database. A custom attribute might be just what you need. You would replace your comment with something like this: [BugFixAttribute(323,"Jesse Liberty","1/1/2005", Comment="Off by one error")] You could then write a program to read through the metadata to find these bug-fix notations and update the database. The attribute would serve the purposes of a comment, but would also allow you to retrieve the information programmatically through tools you'd create. 18.3.1 Declaring an Attribute Attributes, like most things in C#, are embodied in classes. To create a custom attribute, derive your new custom attribute class from System.Attribute: public class BugFixAttribute : System.Attribute You need to tell the compiler which kinds of elements this attribute can be used with (the attribute target). Specify this with (what else?) an attribute: [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] Programming C#, 2nd Edition 415 AttributeUsage is an attribute applied to attributes: a meta-attribute. It provides, if you will, meta-metadata that is, data about the metadata. For the AttributeUsage attribute constructor, you pass two arguments. The first argument is a set of flags that indicate the target in this case, the class and its constructor, fields, methods, and properties. The second argument is a flag that indicates whether a given element might receive more than one such attribute. In this example, AllowMultiple is set to true, indicating that class members can have more than one BugFixAttribute assigned. 18.3.2 Naming an Attribute The new custom attribute in this example is named BugFixAttribute. The convention is to append the word Attribute to your attribute name. The compiler supports this by allowing you to call the attribute with the shorter version of the name. Thus, you can write: [BugFix(123, "Jesse Liberty", "01/01/05", Comment="Off by one")] The compiler will first look for an attribute named BugFix and, if it does not find that, will then look for BugFixAttribute. 18.3.3 Constructing an Attribute Every attribute must have at least one constructor. Attributes take two types of parameters: positional and named. In the BugFix example, the programmer's name and the date are positional parameters, and comment is a named parameter. Positional parameters are passed in through the constructor and must be passed in the order declared in the constructor: public BugFixAttribute(int bugID, string programmer, string date) { this.bugID = bugID; this.programmer = programmer; this.date = date; } Named parameters are implemented as properties: public string Comment { get { return comment; } set { comment = value; } } It is common to create read-only properties for the positional parameters: Programming C#, 2nd Edition 416 public int BugID { get { return bugID; } } 18.3.4 Using an Attribute Once you have defined an attribute, you can put it to work by placing it immediately before its target. To test the BugFixAttribute of the preceding example, the following program creates a simple class named MyMath and gives it two functions. Assign BugFixAttributes to the class to record its code-maintenance history: [BugFixAttribute(121,"Jesse Liberty","01/03/05")] [BugFixAttribute(107,"Jesse Liberty","01/04/05", Comment="Fixed off by one errors")] public class MyMath These attributes will be stored with the metadata. Example 18-1 shows the complete program. Example 18-1. Working with custom attributes namespace Programming_CSharp { using System; using System.Reflection; // create custom attribute to be assigned to class members [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class BugFixAttribute : System.Attribute { // attribute constructor for // positional parameters public BugFixAttribute (int bugID, string programmer, string date) { this.bugID = bugID; this.programmer = programmer; this.date = date; } // accessor public int BugID { get { return bugID; } } Programming C#, 2nd Edition 417 // property for named parameter public string Comment { get { return comment; } set { comment = value; } } // accessor public string Date { get { return date; } } // accessor public string Programmer { get { return programmer; } } // private member data private int bugID; private string comment; private string date; private string programmer; } // ********* assign the attributes to the class ******** [BugFixAttribute(121,"Jesse Liberty","01/03/05")] [BugFixAttribute(107,"Jesse Liberty","01/04/05", Comment="Fixed off by one errors")] public class MyMath { public double DoFunc1(double param1) { return param1 + DoFunc2(param1); } public double DoFunc2(double param1) { return param1 / 3; } } [...]... 6+ 7+ 8+ 9+ 10+ 11+ 12+ 13+ 14+ 15+ 16+ 17+ 18+ 19+ 20+ 21+ 433 Programming C#, 2nd Edition 22+ 23+ 24+ 25+ 26+ 27+ 28+ 29+ 30+ 31+ 32+ 33+ 34+ 35+ 36+ 37+ 38+ 39+ 40+ 41+ 42+ 43+ 44+ 45+ 46+ 47+ 48+ 49+ 50+ 51+ 52+ 53+ 54+ 55+ 56+ 57+ 58+ 59+ 60+ 61+ 62+ 63+ 64+ 65+ 66+ 67+ 68+ 69+ 70+ 71+ 72+ 73+ 74+ 75+ 76+ 77+ 78+ 79+ 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90+ 91+ 92+ 93+ 94+ 95+ 96+ 97+ 98+ 99+... 1 08+ 109+ 110+ 111+ 112+ 113+ 114+ 115+ 116+ 117+ 1 18+ 119+ 120+ 121+ 122+ 123+ 124+ 125+ 126+ 127+ 1 28+ 129+ 130+ 131+ 132+ 133+ 134+ 135+ 136+ 137+ 1 38+ 139+ 140+ 141+ 142+ 143+ 144+ 145+ 146+ 147+ 1 48+ 149+ 150+ 151+ 152+ 153+ 154+ 155+ 156+ 157+ 1 58+ 159+ 160+ 161+ 162+ 163+ 164+ 165+ 166+ 167+ 1 68+ 169+ 170+ 171+ 172+ 173+ 174+ 175+ 176+ 177+ 1 78+ 179+ 180 + 181 + 182 + 183 + 184 + 185 + 186 + 187 + 188 +... printed them one by one The array contained 1,426 entries on my machine 421 Programming C#, 2nd Edition 18. 4.3 Reflecting on a Type You can reflect on a single type in the mscorlib assembly as well To do so, extract a type from the assembly with the GetType( ) method, as shown in Example 18- 4 Example 18- 4 Reflecting on a type namespace Programming_ CSharp { using System; using System.Reflection; } public class... inf.GetCustomAttributes(typeof(BugFixAttribute),false); You can now iterate through this array, printing out the properties of the BugFixAttribute object Example 18- 2 replaces the Tester class from Example 18- 1 419 Programming C#, 2nd Edition Example 18- 2 Using reflection public static void Main( ) { MyMath mm = new MyMath( ); Console.WriteLine("Calling DoFunc(7) Result: {0}", mm.DoFunc1(7)); // get the member... removed the loop: public int DoSum2( ) { return 1+2+3+4+5+6+7 +8+ 9+10+11+12+13+14+15+16+17+ 18+ 19+20; } DoSum2 runs more quickly than DoSum1 does How much more quickly? To find out, you'll need to put a timer on both methods To do so, use a DateTime object to mark the start time and a TimeSpan object to compute the elapsed time 4 28 Programming C#, 2nd Edition For this experiment, you need to create two DoSum(... 18- 8 illustrates the entire test program Example 18- 8 Comparing loop to brute force namespace Programming_ CSharp { using System; using System.Diagnostics; using System.Threading; public class MyMath { // sum numbers with a loop public int DoSum(int n) { int result = 0; for(int i = 1; i . properties of the BugFixAttribute object. Example 18- 2 replaces the Tester class from Example 18- 1. Programming C#, 2nd Edition 420 Example 18- 2. Using reflection public static void Main( ). and those used by the Visual C++ compiler. Programming C#, 2nd Edition 414 Perhaps the attribute you are most likely to use in your everyday C# programming (if you are not interacting with. the attribute targets. Possible attribute targets are detailed in Table 18- 1. Programming C#, 2nd Edition 413 Table 18- 1. Possible attribute targets Member name Usage All Applied to any