Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp And The .NET 2.0 Platform (2005) [eng]

.pdf
Скачиваний:
92
Добавлен:
16.08.2013
Размер:
10.35 Mб
Скачать

74 C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

// Make use of the Console class to perform basic I/O. static void Main(string[] args)

{

// Echo some stats.

Console.Write("Enter your name: "); string s = Console.ReadLine(); Console.WriteLine("Hello, {0} ", s);

Console.Write("Enter your age: "); s = Console.ReadLine();

Console.WriteLine("You are {0} years old", s);

}

Figure 3-5. Basic I/O using System.Console

Formatting Console Output

During these first few chapters, you have seen numerous occurrences of the tokens {0}, {1}, and the like embedded within a string literal. .NET introduces a new style of string formatting, slightly reminiscent of the C printf() function, but without the cryptic %d, %s, or %c flags. A simple example follows (see the output in Figure 3-6):

static void Main(string[] args)

{

...

int theInt = 90;

double theDouble = 9.99; bool theBool = true;

// The '\n' token in a string literal inserts a newline.

Console.WriteLine("Int is: {0}\nDouble is: {1}\nBool is: {2}", theInt, theDouble, theBool);

}

Figure 3-6. Multiple string literal placeholders

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

75

The first parameter to WriteLine() represents a string literal that contains optional placeholders designated by {0}, {1}, {2}, and so forth (curly bracket numbering always begins with zero). The remaining parameters to WriteLine() are simply the values to be inserted into the respective placeholders (in this case, an int, a double, and a bool).

Also be aware that WriteLine() has been overloaded to allow you to specify placeholder values as an array of objects. Thus, you can represent any number of items to be plugged into the format string as follows:

// Fill placeholders using an array of objects.

object[] stuff = {"Hello", 20.9, 1, "There", "83", 99.99933} ; Console.WriteLine("The Stuff: {0} , {1} , {2} , {3} , {4} , {5} ", stuff);

It is also permissible for a given placeholder to repeat within a given string. For example, if you are a Beatles fan and want to build the string "9, Number 9, Number 9" you would write

// John says...

Console.WriteLine("{0}, Number {0}, Number {0}", 9);

Note If you have a mismatch between the number of uniquely numbered curly-bracket placeholders and fill arguments, you will receive a FormatException exception at runtime.

.NET String Formatting Flags

If you require more elaborate formatting, each placeholder can optionally contain various format characters (in either uppercase or lowercase), as seen in Table 3-3.

Table 3-3. .NET String Format Characters

String Format Character

Meaning in Life

C or c

Used to format currency. By default, the flag will prefix the local cultural

 

symbol (a dollar sign [$] for U.S. English).

D or d

Used to format decimal numbers. This flag may also specify the minimum

 

number of digits used to pad the value.

E or e

Used for exponential notation.

F or f

Used for fixed-point formatting.

G or g

Stands for general. This character can be used to format a number to

 

fixed or exponential format.

N or n

Used for basic numerical formatting (with commas).

X or x

Used for hexadecimal formatting. If you use an uppercase X, your hex

 

format will also contain uppercase characters.

 

 

These format characters are suffixed to a given placeholder value using the colon token (e.g., {0:C}, {1:d}, {2:X}, and so on). Assume you have updated Main() with the following logic:

// Now make use of some format tags. static void Main(string[] args)

{

...

Console.WriteLine("C format: {0:C}", 99989.987); Console.WriteLine("D9 format: {0:D9}", 99999); Console.WriteLine("E format: {0:E}", 99999.76543); Console.WriteLine("F3 format: {0:F3}", 99999.9999);

76 C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

Console.WriteLine("N format: {0:N}", 99999); Console.WriteLine("X format: {0:X}", 99999); Console.WriteLine("x format: {0:x}", 99999);

}

Be aware that the use of .NET formatting characters is not limited to console applications. These same flags can be used within the context of the static String.Format() method. This can be helpful when you need to build a string containing numerical values in memory for use in any application type (Windows Forms, ASP.NET, XML web services, and so on):

static void Main(string[] args)

{

...

// Use the static String.Format() method to build a new string. string formatStr;

formatStr =

String.Format("Don't you wish you had {0:C} in your account?", 99989.987);

Console.WriteLine(formatStr);

}

Figure 3-7 shows a test run.

Figure 3-7. String format flags in action

Source Code The BasicConsoleIO project is located under the Chapter 3 subdirectory.

Establishing Member Visibility

Before we go much further, it is important to address the topic of member visibility. Members (methods, fields, constructors, and so on) of a given class or structure must specify their “visibility” level. If you define a member without specifying an accessibility keyword, it automatically defaults to private. C# offers the method access modifiers shown in Table 3-4.

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

77

Table 3-4. C# Accessibility Keywords

C# Access Modifier

Meaning in Life

public

Marks a member as accessible from an object variable as well as any

 

derived classes.

private

Marks a method as accessible only by the class that has defined the

 

method. In C#, all members are private by default.

protected

Marks a method as usable by the defining class, as well as any derived

 

classes. Protected methods, however, are not accessible from an object

 

variable.

internal

Defines a method that is accessible by any type in the same assembly,

 

but not outside the assembly.

protected internal

Defines a method whose access is limited to the current assembly or

 

types derived from the defining class in the current assembly.

 

 

As you may already know, members that are declared public are directly accessible from an object reference via the dot operator (.). Private members cannot be accessed by an object reference, but instead are called internally by the object to help the instance get its work done (i.e., private helper functions).

Protected members are only truly useful when you create class hierarchies, which is the subject of Chapter 4. As far as internal or internal protected members are concerned, they are only truly useful when you are creating .NET code libraries (such as a managed *.dll, a topic examined in Chapter 11).

To illustrate the implications of these keywords, assume you have created a class (SomeClass) using each of the possible member access modifiers:

// Member visibility options. class SomeClass

{

//Accessible anywhere. public void PublicMethod(){}

//Accessible only from SomeClass types. private void PrivateMethod(){}

//Accessible from SomeClass and any descendent. protected void ProtectedMethod(){}

//Accessible from within the same assembly. internal void InternalMethod(){}

//Assembly-protected access.

protected internal void ProtectedInternalMethod(){}

// Unmarked members are private by default in C#. void SomeMethod(){}

}

Now assume you have created an instance of SomeClass and attempt to invoke each method using the dot operator:

static void Main(string[] args)

{

// Make an object and attempt to call members.

SomeClass c = new SomeClass(); c.PublicMethod();

78 C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

c.InternalMethod();

c.ProtectedInternalMethod();

c.PrivateMethod();

// Error!

c.ProtectedMethod();

//

Error!

c.SomeMethod();

//

Error!

}

If you compile this program, you will find that the protected and private members are not accessible from an object.

Source Code The MemberAccess project is located under the Chapter 3 subdirectory.

Establishing Type Visibility

Types (classes, interfaces, structures, enumerations, and delegates) can also take accessibility modifiers, but are limited to public or internal. When you create a public type, you ensure that the type can be accessed from other types in the current assembly as well as external assemblies. Again, this is useful only when you are creating a code library (see Chapter 11); however, here is some example usage:

// This type can be used by any assembly. public class MyClass{}

An internal type, on the other hand, can be used only by the assembly in which it is defined. Thus, if you created a .NET code library that defines three internal types, assemblies that reference the *.dll would not be able to see, create, or in anyway interact with them.

Because internal is the default accessibility for types in C#, if you do not specifically make use of the public keyword, you actually create an internal type:

// These

classes can only be used by the defining assembly.

 

internal

class MyHelperClass{}

 

 

class FinalHelperClass{}

// Types are internal by

default in C#.

Note In Chapter 4 you’ll learn about nested types. As you’ll see, nested types can be declared private as well.

Default Values of Class Member Variables

The member variables of class types are automatically set to an appropriate default value. This value will differ based on the exact data type; however, the rules are simple:

bool types are set to false.

Numeric data is set to 0 (or 0.0 in the case of floating-point data types).

string types are set to null.

char types are set to '\0'.

Reference types are set to null.

Given these rules, ponder the following code:

// Fields of a class type receive automatic default assignments. class Test

{

public int myInt;

// Set to 0.

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

79

public string myString; public bool myBool; public object myObj;

}

//Set to null.

//Set to false.

//Set to null.

Default Values and Local Variables

The story is very different, however, when you declare local variables within a member scope. When you define local variables, you must assign an initial value before you use them, as they do not receive a default assignment. For example, the following code results in a compiler error:

// Compiler error! Must assign 'localInt' to an initial value before use. static void Main(string[] args)

{

int localInt;

Console.WriteLine(localInt);

}

Fixing the problem is trivial. Simply make an initial assignment:

// Better; everyone is happy. static void Main(string[] args)

{

int localInt = 0;

Console.WriteLine(localInt);

}

Note There’s one exception to the mandatory assignment of local variables. If the variable is used as an output parameter (you’ll examine this a bit later in this chapter), the variable doesn’t need to be assigned an initial value.

Member Variable Initialization Syntax

Class types tend to have numerous member variables (aka fields). Given that a class may define multiple constructors, you can find yourself in the annoying position of having to write the same initialization code in each and every constructor implementation. This is particularly true if you do not wish to accept the member’s default value. For example, if you wish to ensure that an integer member variable (myInt) always begins life with the value of 9, you could write

// This is OK, but redundant...

class Test

{

public int myInt; public string myString;

public Test() { myInt = 9; } public Test(string s)

{

myInt = 9; myString = s;

}

}

An alternative would be to define a private helper function for your class type that is called by each constructor. While this will reduce the amount of repeat assignment code, you are now stuck with the following redundancy:

80C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

// This is still rather redundant...

class Test

{

public int myInt; public string myString;

public Test() { InitData(); } public Test(string s)

{

myString = s; InitData();

}

private void InitData() { myInt = 9; }

}

While both of these techniques are still valid, C# allows you to assign a type’s member data to an initial value at the time of declaration (as you may be aware, other OO languages [such as C++] do not allow you to initialize a member in this way). Notice in the following code blurb that member initialization may be used with internal object references as well as numerical data types:

//This technique is useful when you don't want to accept default values

//and would rather not write the same initialization code in each constructor. class Test

{

public int myInt = 9;

public string myStr = "My initial value.";

public SportsCar viper = new SportsCar(Color.Red);

...

}

Note Member assignment happens before constructor logic. Thus, if you assign a field within the scope of a constructor, it effectively cancels out the previous member assignment.

Defining Constant Data

Now that you have seen how to declare class variables, let’s see how to define data that should never be reassigned. C# offers the const keyword to define variables with a fixed, unalterable value. Once the value of a constant has been established, any attempt to alter it results in a compiler error. Unlike in C++, in C# the const keyword cannot be used to qualify parameters or return values, and is reserved for the creation of local or instance-level data.

It is important to understand that the value assigned to a constant variable must be known at compile time, and therefore a constant member cannot be assigned to an object reference (whose value is computed at runtime). To illustrate the use of the const keyword, assume the following class type:

class ConstData

{

//The value assigned to a const must be known

//at compile time.

public const string BestNbaTeam = "Timberwolves"; public const double SimplePI = 3.14;

public const bool Truth = true; public const bool Falsity = !Truth;

}

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

81

Notice that the value of each constant is known at the time of compilation. In fact, if you were to view these constants using ildasm.exe, you would find the value hard-coded directly into the assembly, as shown in Figure 3-8. (You can’t get much more constant than this!)

Figure 3-8. The const keyword hard-codes its value into the assembly metadata.

Referencing Constant Data

When you wish to reference a constant defined by an external type, you must prefix the defining type name (e.g., ConstData.Truth), as constant fields are implicitly static. However, if you are referencing a piece of constant data defined in the current type (or within the current member), you are not required to prefix the type name. To solidify these points, observe the following additional class:

class Program

{

public const string BestNhlTeam = "Wild";

static void Main(string[] args)

{

//Print const values defined by other type.

Console.WriteLine("Nba const: {0}", ConstData.BestNbaTeam); Console.WriteLine("SimplePI const: {0}", ConstData.SimplePI); Console.WriteLine("Truth const: {0}", ConstData.Truth); Console.WriteLine("Falsity const: {0}", ConstData.Falsity);

//Print member-level const.

Console.WriteLine("Nhl const: {0}", BestNhlTeam);

// Print local-scoped const. const int LocalFixedValue = 4;

Console.WriteLine("Local const: {0}", LocalFixedValue); Console.ReadLine();

}

}

Notice that when the Program class accesses the constants within ConstData, the type name must be specified. However, Program has direct access to the BestNhlTeam constant as it was defined within its own class scope. The LocalFixedValue constant defined within Main() would, of course, be accessible only from the Main() method.

Source Code The Constants project is located under the Chapter 3 subdirectory.

82 C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

Defining Read-Only Fields

As mentioned earlier, the value assigned to a constant must be known at compile time. However, what if you wish to create an unchangeable field whose initial value is not known until runtime? Assume you have created a class named Tire, which maintains a manufacture ID. Furthermore, assume you wish to configure this class type to maintain a pair of well-known Tire instances whose value should never change. If you use the const keyword, you will receive compiler errors, given that the address of an object in memory is not known until runtime:

class Tire

{

//Given that the address of objects is determined at

//runtime, we cannot use the 'const' keyword here!

public

const

Tire

GoodStone = new Tire(90);

//

Error!

public

const

Tire

FireYear = new Tire(100);

//

Error!

public int manufactureID; public Tire() {}

public Tire(int ID)

{ manufactureID = ID; }

}

Read-only fields allow you to establish a point of data whose value is not known at compile time, but that should never change once established. To define a read-only field, make use of the C# readonly keyword:

class Tire

{

public readonly Tire GoodStone = new Tire(90); public readonly Tire FireYear = new Tire(100);

public int manufactureID; public Tire() {}

public Tire(int ID)

{ manufactureID = ID; }

}

With this update, you not only compile, but also ensure that if the GoodStone or FireYear fields are changed within your program, you receive a compilation error:

static void Main(string[] args)

{

// Error! Can't change the value of a read-only field.

Tire t = new Tire(); t.FireYear = new Tire(33);

}

Read-only fields have another distinction from constant data: their value may be assigned within the scope of a constructor. This can be very useful if the value to assign to a read-only field must be read from an external source (such as a text file or database). Assume another class named Employee, which defines a read-only string representing a U.S. Social Security number (SSN). To ensure the object user can specify this value, you may author the following code:

class Employee

{

public readonly string SSN;

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

83

public Employee(string empSSN)

{

SSN = empSSN;

}

}

Again, because SSN is readonly, any attempt to change this value after the constructor logic results in a compiler error:

static void Main(string[] args)

{

Employee e = new Employee("111-22-1111"); e.SSN = "222-22-2222"; // Error!

}

Static Read-Only Fields

Unlike constant data, read-only fields are not implicitly static. If you wish to allow object users to obtain the value of a read-only field from the class level, simply make use of the static keyword:

class Tire

{

public static readonly Tire GoodStone = new Tire(90); public static readonly Tire FireYear = new Tire(100);

...

}

Here is an example of working with the new Tire type:

static void Main(string[] args)

{

Tire myTire = Tire.FireYear;

Console.WriteLine("ID of my tire is: {0}", myTire.manufactureID);

}

Source Code The ReadOnlyFields project is included under the Chapter 3 subdirectory.

Understanding the static Keyword

As shown throughout this chapter, C# class (and structure) members may be defined using the static keyword. When you do so, the member in question must be invoked directly from the class level, rather than from a type instance. To illustrate the distinction, consider our good friend System.Console. As you have seen, you do not invoke the WriteLine() method from the object level:

// Error! WriteLine() is not an instance level method!

Console c = new Console(); c.WriteLine("I can't be printed...");

but instead simply prefix the type name to the static WriteLine() member:

// Correct! WriteLine() is a static method.

Console.WriteLine("Thanks...");

Simply put, static members are items that are deemed (by the type designer) to be so commonplace that there is no need to create an instance of the type. When you are designing custom class types, you are also able to define any number of static and/or instance-level members.