// NOTE: C# doesn’t allow you to specify static for constants
// because constants are always implicitly static.
public const Int32 MaxEntriesInList = 50;
}
Then use the following code to build an application:
using System;
class App {
static void Main() {
Console.WriteLine("Max entries supported in list: "
+ Component.MaxEntriesInList);
}
}
You’ll notice that this application code references the MaxEntriesInList constant. When the compiler builds the application code, it sees that MaxEntriesInList is a constant literal with a value of 50 and embeds the Int32 value of 50 right inside the application’s IL code. In fact, after building the application code, the DLL assembly isn’t even loaded at run time and can be deleted from the disk.
This example should make the versioning problem obvious to you. If the developer changes the MaxEntriesInList constant to 1000 and rebuilds the DLL assembly, the application code is not affected. For the application to pick up the new value, it would have to be recompiled as well. You can’t use constants if you need to have a value in one module picked up by another module at run time (instead of compile time). Instead, you can use read-only fields, which I’ll discuss next.
Fields
A field is a data member that holds an instance of a value type or a reference to a reference type. The CLR supports both type (static) and instance (nonstatic) fields. For type fields, the dynamic memory to hold the field is allocated when the type is loaded into an AppDomain (see Chapter 20), which typically happens the first time any method that references the type is JIT compiled. For instance fields, the dynamic memory to hold the field is allocated when an instance of the type is constructed.
Because fields are stored in dynamic memory, their value can be obtained at run time only. Fields also solve the versioning problem that exists with constants. In addition, a field can be of any data type, so you don’t have to restrict yourself to your compiler’s built-in primitive types (as you do for constants).
The CLR supports read-only fields and read/write fields. Most fields are read/write fields, meaning that the field’s value might change multiple times as the code executes. However, read-only fields can be written to only within a constructor method (which is called only once, when an object is first created). Compilers and verification ensure that read-only fields are not written to by any method other than a constructor.
Let’s take the example from the “Constants” section and fix the versioning problem by using a static read-only field. Here’s the new version of the DLL assembly’s code:
using System;
public class Component {
// The static is required to associate the field with the type.
public static readonly Int32 MaxEntriesInList = 50;
}
This is the only change you have to make; the application code doesn’t have to change at all, although you must rebuild it to see the new behavior. Now, when the application’s Main
method runs, the CLR will load the DLL assembly (so this assembly is now required at run time) and grab the value of the MaxEntriesInList field out of the dynamic memory that was allocated for it. Of course, the value will be 50.
Let’s say that the developer of the DLL assembly changes the 50 to 1000 and rebuilds the assembly. When the application code is reexecuted, it will automatically pick up the new value: 1000. In this case, the application code doesn’t have to be rebuilt—it just works (although its performance is adversely affected). A caveat: this scenario assumes that the new version of the DLL assembly is not strongly named or that the versioning policy of the application is such that the CLR loads this new version.
The preceding example shows how to define a read-only static field that is associated with the type itself. You can also define read/write static fields and read-only and read/write instance fields, as shown here:
// This is a static read-only field; its value is calculated and
// stored in memory when this class is initialized at run time.
public static readonly Random random = new Random();
// This is a static read/write field.
static Int32 numberOfWrites = 0;
// This is an instance read-only field.
public String readonly pathName = "Untitled";
// This is an instance read/write field.
public FileStream fs;
public SomeType(String pathName) {
// This line changes a read-only field.
// This is OK because the code is in a constructor.
this.pathName = pathName;
}
public String DoSomething() {
// This line reads and writes to the static read/write field.
numberOfWrites = numberOfWrites + 1;
// This line reads the read-only instance field.
return pathName;
}
}
In this code, many of the fields are initialized inline. C# allows you to use this convenient inline initialization syntax to initialize a type’s constants, read/write, and read-only fields. As you’ll see in Chapter 9, C# treats initializing a field inline as shorthand syntax for initializing the field in a constructor.
Chapter 9: Methods
In this chapter, I’ll talk about the different kinds of methods a type can define and the various issues related to methods. Specifically, I’ll show you how to define constructor methods (both instance and type), operator overload methods, and conversion operator methods (for implicit and explicit casting). In addition, I’ll cover how to pass parameters by reference to a method and how to define methods that accept a variable number of parameters. Finally, I’ll explain the virtual method versioning mechanism that exists to stop the potential for application instability when a base class’s programming interface has changed.