Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Fields

A field is a data member that holds an instance of a value type or a reference to a reference type.

Table 7-1 shows the modifiers that can be applied to a field.

 

TABLE 7-1Field Modifiers

 

CLR Term C# Term Description
Static static The field is part of the type’s state, as opposed to being part of an object’s state.
Instance (default) The field is associated with an instance of the type, not the type itself.
InitOnly readonly The field can be written to only by code contained in a constructor method.
Volatile volatile Code that accessed the field is not subject to some thread-unsafe optimizations that may be performed by the compiler, the CLR, or by hardware. Only the following types can be marked volatile: all reference types, Single, Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Char, and all enumerated types with an underlying type of Byte, SByte, Int16, UInt16, Int32, or UInt32. Volatile fields are discussed in Chapter 29, “Primitive Thread Synchronization Constructs.”

 

As Table 7-1 shows, the common language run time (CLR) supports both type (static) and instance (nonstatic) fields. For type fields, the dynamic memory required to hold the field’s data is allocated inside the type object, which is created when the type is loaded into an AppDomain (see Chapter 22, “CLR Hosting and AppDomains”), which typically happens the first time any method that references the type is just-in-time (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 readonly fields and read/write fields. Most fields are read/write fields, meaning the field’s value might change multiple times as the code executes. However, readonly 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 readonly fields are not written to by any method other than a constructor. Note that reflection can be used to modify a readonly field.

Let’s take the example from the “Constants” section and fix the versioning problem by using a

static readonly field. Here’s the new version of the DLL assembly’s code.

 

using System;

 

public sealed class SomeLibraryType {

// 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, al- though 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 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 assem- bly. When the application code is re-executed, 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 and the versioning policy of the application is such that the CLR loads this new version.

 

The following example shows how to define a readonly static field that is associated with the type itself, as well as read/write static fields and readonly and read/write instance fields, as shown here.

 

public sealed class SomeType {

// 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 s_random = new Random();

 

// This is a static read/write field. private static Int32 s_numberOfWrites = 0;

 

// This is an instance read­only field. public readonly String Pathname = "Untitled";

 

// This is an instance read/write field. private System.IO.FileStream m_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. s_numberOfWrites = s_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 class’s constants and read/write and readonly fields. As you’ll see in Chapter 8, “Methods,” C# treats initializing a field inline as shorthand syntax for initializing the field in a constructor. Also, in C#, there are some performance issues to consider when initializing fields by using inline syntax versus assignment syntax in a constructor. These performance issues are discussed in Chapter 8 as well.

       
   
 
 


 


C HA P T E R 8

Methods

In this chapter:

Instance Constructors and Classes (Reference Types).............................. 181

Instance Constructors and Structures (Value Types)................................ 184

Type Constructors................................................................................... 187

Operator Overload Methods................................................................... 191

Conversion Operator Methods................................................................ 195

Extension Methods................................................................................. 198

Partial Methods...................................................................................... 204

 

This chapter focuses on the various kinds of methods that you’ll run into, including instance construc- tors and type constructors, as well as how to define methods to overload operators and type conver- sions (for implicit and explicit casting). We’ll also talk about extension methods, which allow you to logically add your own instance methods to already existing types, and partial methods, which allow you to spread a type’s implementation into multiple parts.

 

 

 
 

Instance Constructors and Classes (Reference Types)

Constructors are special methods that allow an instance of a type to be initialized to a good state. Constructor methods are always called .ctor (for constructor) in a method definition metadata table. When creating an instance of a reference type, memory is allocated for the instance’s data fields, the object’s overhead fields (type object pointer and sync block index) are initialized, and then the type’s instance constructor is called to set the initial state of the object.

When constructing a reference type object, the memory allocated for the object is always zeroed out before the type’s instance constructor is called. Any fields that the constructor doesn’t explicitly overwrite are guaranteed to have a value of 0 or null.

Unlike other methods, instance constructors are never inherited. That is, a class has only the in- stance constructors that the class itself defines. Because instance constructors are never inherited, you cannot apply the following modifiers to an instance constructor: virtual, new, override, sealed, or abstract. If you define a class that does not explicitly define any constructors, the C# compiler defines a default (parameterless) constructor for you whose implementation simply calls the base class’s parameterless constructor.


For example, if you define the following class.

 

public class SomeType {

}

 

it is as though you wrote the code as follows.

 

public class SomeType {

public SomeType() : base() { }

}

 

If the class is abstract, the compiler-produced default constructor has protected accessibility; otherwise, the constructor is given public accessibility. If the base class doesn’t offer a parameterless constructor, the derived class must explicitly call a base class constructor or the compiler will issue an error. If the class is static (sealed and abstract), the compiler will not emit a default constructor at all into the class definition.

A type can define several instance constructors. Each constructor must have a different signature, and each can have different accessibility. For verifiable code, a class’s instance constructor must call its base class’s constructor before accessing any of the inherited fields of the base class. The C# compiler will generate a call to the default base class’s constructor automatically if the derived class’s construc- tor does not explicitly invoke one of the base class’s constructors. Ultimately, System.Object’s public, parameterless constructor gets called. This constructor does nothing—it simply returns. This is because System.Object defines no instance data fields, and therefore its constructor has nothing to do.

In a few situations, an instance of a type can be created without an instance constructor being called. In particular, calling Object’s MemberwiseClone method allocates memory, initializes the ob- ject’s overhead fields, and then copies the source object’s bytes to the new object. Also, a constructor is usually not called when deserializing an object with the runtime serializer. The deserialization code allocates memory for the object without calling a constructor by using the System.Runtime.Seri­ alization.FormatterServices type's GetUninitializedObject or GetSafeUninitialized­ Object methods (as discussed in Chapter 24, “Runtime Serialization”).

       
   
 
 

 

C# offers a simple syntax that allows the initialization of fields defined within a reference type

when an instance of the type is constructed.

 

internal sealed class SomeType { private Int32 m_x = 5;

}


When a SomeType object is constructed, its m_x field will be initialized to 5. How does this hap- pen? Well, if you examine the Intermediate Language (IL) for SomeType’s constructor method (also called .ctor), you’ll see the code shown here.

 

.method public hidebysig specialname rtspecialname instance void .ctor() cil managed

{

// Code size 14 (0xe)

.maxstack 8 IL_0000: ldarg.0 IL_0001: ldc.i4.5

IL_0002: stfld int32 SomeType::m_x IL_0007: ldarg.0

IL_0008: call instance void [mscorlib]System.Object::.ctor() IL_000d: ret

} // end of method SomeType::.ctor

 

In this code, you see that SomeType’s constructor contains code to store a 5 into m_x and then calls the base class’s constructor. In other words, the C# compiler allows the convenient syntax that lets you initialize the instance fields inline and translates this to code in the constructor method to perform the initialization. This means that you should be aware of code explosion, as illustrated by the following class definition.

 

internal sealed class SomeType { private Int32 m_x = 5;

private String m_s = "Hi there"; private Double m_d = 3.14159; private Byte m_b;

 

// Here are some constructors. public SomeType() { ... } public SomeType(Int32 x) { ... }

public SomeType(String s) { ...; m_d = 10; }

}

 

When the compiler generates code for the three constructor methods, the beginning of each method includes the code to initialize m_x, m_s, and m_d. After this initialization code, the compiler inserts a call to the base class’s constructor, and then the compiler appends to the method the code that appears in the constructor methods. For example, the code generated for the constructor that takes a String parameter includes the code to initialize m_x, m_s, and m_d, call the base class’s (Object’s) constructor, and then overwrite m_d with the value 10. Note that m_b is guaranteed to be initialized to 0 even though no code exists to explicitly initialize it.

       
   
 
 


Because there are three constructors in the preceding class, the compiler generates the code to initialize m_x, m_s, and m_d three times—once per constructor. If you have several initialized instance fields and a lot of overloaded constructor methods, you should consider defining the fields without the initialization, creating a single constructor that performs the common initialization, and having each constructor explicitly call the common initialization constructor. This approach will reduce the size of the generated code. Here is an example using C#’s ability to explicitly have a constructor call another constructor by using the this keyword.

 

internal sealed class SomeType {

// Do not explicitly initialize the fields here. private Int32 m_x;

private String m_s; private Double m_d; private Byte m_b;

 

// This constructor sets all fields to their default.

// All of the other constructors explicitly invoke this constructor. public SomeType() {

m_x = 5;

m_s = "Hi there"; m_d = 3.14159;

m_b = 0xff;

}

 

// This constructor sets all fields to their default, then changes m_x. public SomeType(Int32 x) : this() {

m_x = x;

}

 

// This constructor sets all fields to their default, then changes m_s. public SomeType(String s) : this() {

m_s = s;

}

 

// This constructor sets all fields to their default, then changes m_x & m_s. public SomeType(Int32 x, String s) : this() {

m_x = x; m_s = s;

}

}

 

 

 
 

Instance Constructors and Structures (Value Types)

Value type (struct) constructors work quite differently from reference type (class) constructors. The common language runtime (CLR) always allows the creation of value type instances, and there is no way to prevent a value type from being instantiated. For this reason, value types don’t actu- ally even need to have a constructor defined within them, and the C# compiler doesn't emit default parameterless constructors for value types. Examine the following code.


internal struct Point { public Int32 m_x, m_y;

}

internal sealed class Rectangle {

public Point m_topLeft, m_bottomRight;

}

 

To construct a Rectangle, the new operator must be used, and a constructor must be specified. In this case, the default constructor automatically generated by the C# compiler is called. When memory is allocated for the Rectangle, the memory includes the two instances of the Point value type. For performance reasons, the CLR doesn’t attempt to call a constructor for each value type field con- tained within the reference type. But as I mentioned earlier, the fields of the value types are initialized to 0/null.

The CLR does allow you to define constructors on value types. The only way that these construc- tors will execute is if you write code to explicitly call one of them, as in Rectangle’s constructor, shown here.

 

internal struct Point { public Int32 m_x, m_y;

 

public Point(Int32 x, Int32 y) { m_x = x;

m_y = y;

}

}

 

internal sealed class Rectangle {

public Point m_topLeft, m_bottomRight;

 

public Rectangle() {

// In C#, new on a value type calls the constructor to

// initialize the value type's fields. m_topLeft = new Point(1, 2); m_bottomRight = new Point(100, 200);

}

}

 

A value type’s instance constructor is executed only when explicitly called. So if Rectangle’s con- structor didn’t initialize its m_topLeft and m_bottomRight fields by using the new operator to call Point’s constructor, the m_x and m_y fields in both Point fields would be 0.

In the Point value type defined earlier, no default parameterless constructor is defined. However,

let’s rewrite that code as follows.

 

internal struct Point { public Int32 m_x, m_y;

 

public Point() { m_x = m_y = 5;

}

}


internal sealed class Rectangle {

public Point m_topLeft, m_bottomRight;

 

public Rectangle() {

}

}

 

Now when a new Rectangle is constructed, what do you think the m_x and m_y fields in the two Point fields, m_topLeft and m_bottomRight, would be initialized to: 0 or 5? (Hint: This is a trick question.)

Many developers (especially those with a C++ background) would expect the C# compiler to emit code in Rectangle’s constructor that automatically calls Point’s default parameterless constructor for the Rectangle’s two fields. However, to improve the run-time performance of the application, the C# compiler doesn’t automatically emit this code. In fact, many compilers will never emit code to call a value type’s default constructor automatically, even if the value type offers a parameterless

constructor. To have a value type’s parameterless constructor execute, the developer must add explicit code to call a value type’s constructor.

Based on the information in the preceding paragraph, you should expect the m_x and m_y fields in Rectangle’s two Point fields to be initialized to 0 in the code shown earlier because there are no explicit calls to Point’s constructor anywhere in the code.

However, I did say that my original question was a trick question. The trick part is that C# doesn’t allow a value type to define a parameterless constructor. So the previous code won’t actually com- pile. The C# compiler produces the following message when attempting to compile that code: error CS0568: Structs cannot contain explicit parameterless constructors.

 

C# purposely disallows value types from defining parameterless constructors to remove any confusion a developer might have about when that constructor gets called. If the constructor can’t be defined, the compiler can never generate code to call it automatically. Without a parameterless constructor, a value type’s fields are always initialized to 0/null.

       
   
 
 

 

Keep in mind that although C# doesn’t allow value types with parameterless constructors, the CLR does. So if the unobvious behavior described earlier doesn’t bother you, you can use another pro- gramming language (such as IL assembly language) to define your value type with a parameterless constructor.


Because C# doesn’t allow value types with parameterless constructors, compiling the following type produces the following message: error CS0573: 'SomeValType.m_x': cannot have in­ stance field initializers in structs.

 

internal struct SomeValType {

// You cannot do inline instance field initialization in a value type. private Int32 m_x = 5;

}

 

In addition, because verifiable code requires that every field of a value type be written to prior to any field being read, any constructors that you do have for a value type must initialize all of the type’s fields. The following type defines a constructor for the value type but fails to initialize all of the fields.

 

internal struct SomeValType { private Int32 m_x, m_y;

 

// C# allows value types to have constructors that take parameters. public SomeValType(Int32 x) {

m_x = x;

// Notice that m_y is not initialized here.

}

}

 

When compiling this type, the C# compiler produces the following message: error CS0171: Field 'SomeValType.m_y' must be fully assigned before control leaves the con­ structor. To fix the problem, assign a value (usually 0) to y in the constructor.

As an alternative way to initialize all the fields of a value type, you can actually do the following.

 

// C# allows value types to have constructors that take parameters. public SomeValType(Int32 x) {

// Looks strange but compiles fine and initializes all fields to 0/null. this = new SomeValType();

 

m_x = x; // Overwrite m_x's 0 with x

// Notice that m_y was initialized to 0.

}

 

In a value type’s constructor, this represents an instance of the value type itself and you can actu- ally assign to it the result of newing up an instance of the value type, which really just zeroes out all the fields. In a reference type’s constructor, this is considered read-only, so you cannot assign to it at all.

 

 


Date: 2016-03-03; view: 576


<== previous page | next page ==>
Nbsp;   Constants | Nbsp;   Type Constructors
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.018 sec.)