Siêu thị PDFTải ngay đi em, trời tối mất

Thư viện tri thức trực tuyến

Kho tài liệu với 50,000+ tài liệu học thuật

© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

Programming C# 4.0 phần 2 ppt
PREMIUM
Số trang
78
Kích thước
10.4 MB
Định dạng
PDF
Lượt xem
1999

Programming C# 4.0 phần 2 ppt

Nội dung xem thử

Mô tả chi tiết

Defining Classes

We can start out with the simplest possible class. It will have no methods, and no data,

so as a model of a plane in our system, it leaves something to be desired, but it gets us

started.

If you want to build your own version as you read, create a new Console Application

project just as we did in Chapter 2. To add a new class, use the Project→Add Class

menu item (or right-click on the project in the Solution Explorer and select Add→Class).

It’ll add a new file for the class, and if we call it Plane.cs, Visual Studio will create a new

source file with the usual using directives and namespace declaration. And most im￾portantly, the file will contain a new, empty class definition, as shown in Example 3-1.

Example 3-1. The empty Plane class

class Plane

{

}

Right; if we look back at the specification, there’s clearly a whole bunch of information

we’ve got about the plane that we need to store somewhere. C# gives us a handy

mechanism for this called a property.

Representing State with Properties

Each plane has an identifier which is just a string of letters and numbers. We’ve already

seen a built-in type ideal for representing this kind of data: string. So, we can add a

property called Identifier, of type string, as Example 3-2 shows.

Example 3-2. Adding a property

class Plane

{

string Identifier

{

get;

set;

}

}

A property definition always states the type of data the property holds (string in this

case), followed by its name. By convention, we use PascalCasing for this name—see

the sidebar on the next page. As with most nontrivial elements of a C# program, this

is followed by a pair of braces, and inside these we say that we want to provide a get￾ter and a set-ter for the property. You might be wondering why we need to declare

these—wouldn’t any property need to be gettable and settable? But as we’ll see, these

explicit declarations turn out to be useful.

64 | Chapter 3: Abstracting Ideas with Classes and Structs

PascalCasing and camelCasing

Most programming languages, including C#, use whitespace to separate elements of

the code—it must be clear where one statement (or keyword, variable, or whatever)

ends and the next begins, and we often rely on spaces to mark the boundaries. But this

gives us a problem when it comes to naming. Lots of features of a program have

names—classes, methods, properties, and variables, for example—and we might want

to use multiple words in a name. But we can’t put a space in the middle of a name like

this:

class Jumbo Jet

{

}

The C# compiler would complain—the space after Jumbo marks the end of the name,

and the compiler doesn’t understand why we’ve put a second name, Jet, after that. If

we want to use multiple words in a name, we have to do it without using spaces. C#

programmers conventionally use two styles of capitalization to put multiple words in

a name:

• PascalCasing, where each word starts with a capital letter. This is used for types,

properties, and methods.

• camelCasing, where the first word starts with a lowercase letter and all subsequent

words get a capital. This is used for parameters and fields.

Pascal casing takes its name from the fact that it was a popular style among Pascal

programmers. It’s not a widely used language today, but lots of developers cut their

teeth on it a decade or three ago when drainpipe trousers, trilby hats, and black-and￾white print T-shirts were the latest in fashion (or at least, they were in parts of Europe).

And, by no coincidence whatsoever, Anders Hejlsberg (a key figure in the C# design

team) also designed Borland’s Turbo Pascal.

As for camel casing, that name comes from the fact that uppercase letters only ever

appear in the middle of the name, meaning you get one or more humps in the middle,

like a camel.

There’s a wrinkle in these conventions. Acronyms generally get treated as though they

are words, so if you had a class for an RGB color you might call it ColorRgb, and a color

with an alpha channel might be ColorArgb. (The .NET Framework class libraries include

types that refer to Argb, and people often mistakenly think that the “Arg” is short for

“argument” rather than Alpha, Red, Green, and Blue.)

There’s an exception to this exception: two-letter acronyms are usually capitalized. So

a person’s intelligence quotient might be recorded as PersonIQ.

These naming conventions are optional, but strongly recommended to help people

understand your code. MSDN offers an extensive set of guidelines for these sorts of

conventions at http://msdn.microsoft.com/library/ms229042.

Defining Classes | 65

If we create an instance of this class, we could use this Identifier property to get and

set its identifier. Example 3-3 shows this in a modified version of the Main function in

our Program.cs file.

Example 3-3. Using the Plane class’s property

static void Main(string[] args)

{

Plane someBoeing777 = new Plane();

someBoeing777.Identifier = "BA0049";

Console.WriteLine(

"Your plane has identifier {0}",

someBoeing777.Identifier);

// Wait for the user to press a key, so

// that we can see what happened

Console.ReadKey();

}

But wait! If you try to compile this, you end up with an error message:

'Plane.Identifier' is inaccessible due to its protection level

What’s that all about?

Protection Levels

Earlier, we mentioned that one of the objectives of good design is encapsulation: hiding

the implementation details so that other developers can use our objects without relying

on (or knowing about) how they work. As the error we just saw in Example 3-3 shows,

a class’s members are hidden by default. If we want them to be visible to users of our

class, we must change their protection level.

Every entity that we declare has its own protection level, whether we specify it or not.

A class, for example, has a default protection level called internal. This means that it

can only be seen by other classes in its own assembly. We’ll talk a lot more about

assemblies in Chapter 15. For now, though, we’re only using one assembly (our ex￾ample application itself), so we can leave the class at its default protection level.

While classes default to being internal, the default protection level for a class member

(such as a property) is private. This means that it is only accessible to other members

of the class. To make it accessible from outside the class, we need to change its protec￾tion level to public, as Example 3-4 shows.

Example 3-4. Making a property public

class Plane

{

public string Identifier

{

66 | Chapter 3: Abstracting Ideas with Classes and Structs

get;

set;

}

}

Now when we compile and run the application, we see the correct output:

Your plane has identifier BA0049

Notice how this is an opt-in scheme. If you don’t do anything to the contrary, you get

the lowest sensible visibility. Your classes are visible to any code inside your assembly,

but aren’t accessible to anyone else; a class’s properties and methods are only visible

inside the class, unless you explicitly choose to make them more widely accessible.

When different layers specify different protection, the effective accessibility is the low￾est specified. For example, although our property has public accessibility, the class of

which it is a member has internal accessibility. The lower of the two wins, so the

Identifier property is, in practice, only accessible to code in the same assembly.

It is a good practice to design your classes with the smallest possible public interface

(part of something we sometimes call “minimizing the surface area”). This makes it

easier for clients to understand how they’re supposed to be used and often cuts down

on the amount of testing you need to do. Having a clean, simple public API can also

improve the security characteristics of your class framework, because the larger and

more complex the API gets, the harder it generally gets to spot all the possible lines of

attack.

That being said, there’s a common misconception that accessibility modifiers “secure”

your class, by preventing people from accessing private members. Hence this warning:

It is important to recognize that these protection levels are a convenient

design constraint, to help us structure our applications properly. They

are not a security feature. It’s possible to use the reflection features de￾scribed in Chapter 17 to circumvent these constraints and to access these

supposedly hidden details.

To finish this discussion, you should know that there are two other protection levels

available to us—protected and protected internal—which we can use to expose (or

hide) members to developers who derive new classes from our class without making

the members visible to all. But since we won’t be talking about derived classes until

Chapter 4, we’ll defer the discussion of these protection levels until then.

We can take advantage of protection in our Plane class. A plane’s identifier shouldn’t

change mid-flight, and it’s a good practice for code to prevent things from happening

that we know shouldn’t happen. We should therefore add that constraint to our class.

Fortunately, we have the ability to change the accessibility of the getter and the setter

individually, as Example 3-5 shows. (This is one reason the property syntax makes use

declare the get and set explicitly—it gives us a place to put the protection level.)

Defining Classes | 67

Example 3-5. Making a property setter private

class Plane

{

public string Identifier

{

get;

private set;

}

}

Compiling again, we get a new error message:

The property or indexer 'Plane.Identifier' cannot be used in this context because

the set accessor is inaccessible

The problem is with this bit of code from Example 3-3:

someBoeing777.Identifier = "BA0049";

We’re no longer able to set the property, because we’ve made the setter private (which

means that we can only set it from other members of our class). We wanted to prevent

the property from changing, but we’ve gone too far: we don’t even have a way of giving

it a value in the first place. Fortunately, there’s a language feature that’s perfect for this

situation: a constructor.

Initializing with a Constructor

A constructor is a special method which allows you to perform some “setup” when you

create an instance of a class. Just like any other method, you can provide it with pa￾rameters, but it doesn’t have an explicit return value. Constructors always have the

same name as their containing class.

Example 3-6 adds a constructor that takes the plane’s identifier. Because the construc￾tor is a member of the class, it’s allowed to use the Identifier property’s private setter.

Example 3-6. Defining a constructor

class Plane

{

public Plane(string newIdentifier)

{

Identifier = newIdentifier;

}

public string Identifier

{

get;

private set;

}

}

68 | Chapter 3: Abstracting Ideas with Classes and Structs

Notice how the constructor looks like a standard method declaration, except that since

there’s no need for a return type specifier, we leave that out. We don’t even write

void, like we would for a normal method that returns nothing. And it would be weird

if we did; in a sense this does return something—the newly created Plane—it just does

so implicitly.

What sort of work should you do in a constructor? Opinion is divided on the subject—

should you do everything required to make the object ready to use, or the minimum

necessary to make it safe? The truth is that it is a judgment call—there are no hard and

fast rules. Developers tend to think of a constructor as being a relatively low-cost op￾eration, so enormous amounts of heavy lifting (opening files, reading data) might be a

bad idea. Getting the object into a fit state for use is a good objective, though, because

requiring other functions to be called before the object is fully operational tends to lead

to bugs.

We need to update our Main function to use this new constructor and to get rid of the

line of code that was setting the property, as Example 3-7 shows.

Example 3-7. Using a constructor

static void Main(string[] args)

{

Plane someBoeing777 = new Plane("BA0049");

Console.WriteLine(

"Your plane has identifier {0}",

someBoeing777.Identifier);

Console.ReadKey();

}

Notice how we pass the argument to the constructor inside the parentheses, in much

the same way that we pass arguments in a normal method call.

If you compile and run that, you’ll see the same output as before—but now we have

an identifier that can’t be changed by users of the object.

Be very careful when you talk about properties that “can’t be changed”

because they have a private setter. Even if you can’t set a property, you

may still be able to modify the state of the object referred to by that

property. The built-in string type happens to be immune to that be￾cause it is immutable (i.e., it can’t be changed once it has been created),

so making the setter on a string property private does actually prevent

clients from changing the property, but most types aren’t like that.

Speaking of properties that might need to change, our specification requires us to know

the speed at which each plane is traveling. Sadly, our specification didn’t mention the

units in which we were expected to express that speed. Let’s assume it is miles per hour,

Defining Classes | 69

and add a suitable property. We’ll use the floating-point double data type for this.

Example 3-8 shows the code to add to Plane.

Example 3-8. A modifiable speed property

public double SpeedInMilesPerHour

{

get;

set;

}

If we were to review this design with the customer, they might point out that while they

have some systems that do indeed want the speed in miles per hour the people they

liaise with in European air traffic control want the speed in kilometers per hour. To

avoid confusion, we will add another property so that they can get or set the speed in

the units with which they are familiar. Example 3-9 shows a suitable property.

Example 3-9. Property with code in its get and set

public double SpeedInKilometersPerHour

{

get

{

return SpeedInMilesPerHour * 1.609344;

}

set

{

SpeedInMilesPerHour = value / 1.609344;

}

}

We’ve done something different here—rather than just writing get; and set; we’ve

provided code for these accessors. This is another reason we have to declare the acces￾sors explicitly—the C# compiler needs to know whether we want to write a custom

property implementation.

We don’t want to use an ordinary property in Example 3-9, because our SpeedInKilo

metersPerHour is not really a property in its own right—it’s an alternative representation

for the information stored in the SpeedInMilesPerHour property. If we used the normal

property syntax for both, it would be possible to set the speed as being both 100 mph

and 400 km/h, which would clearly be inconsistent. So instead we’ve chosen to im￾plement SpeedInKilometersPerHour as a wrapper around the SpeedInMilesPerHour

property.

If you look at the getter, you’ll see that it returns a value of type double. It is equivalent

to a function with this signature:

public double get_SpeedInKilometersPerHour()

70 | Chapter 3: Abstracting Ideas with Classes and Structs

Tải ngay đi em, còn do dự, trời tối mất!