A lot of C# developers run into trouble with the private keyword. This post from the author of Head First C# shows that it's not hard to understand the mechanics of the private keyword.
A reader on the Head First C# forum posted a question. It's actually a really good question, one that a lot of developers ask once they get past the basics of straightforward coding and syntax and start thinking about what it takes to make better programs:
I'm still having trouble understanding why you make some things private vs public. For example I see many private randoms/Lists<> being put in the constructor. In the Go Fish exercise the game class is private.
Can anyone explain why you would add a private class to a form? I am having a hard time understanding how a program "fits together" (like using a game class in Lab #2).
This reader's specifically referring to the "Go Fish" game we build in chapter 8 of the book (but don't worry, you don't need to read the book in order to understand this blog post). In the program, the main form has a private field called game that holds a reference to a
But he probably already figured that part out. It's not hard to understand the mechanics of the private keyword. But that's not the part that's interesting -- or the part that will make you a better developer. The interesting part is understanding why you want to use the private keyword.
Why would I make things private? Why do I care about the private keyword at all?
This is a really good question that a lot of developers ask themselves -- especially when they get the hang of using classes and objects, and start building bigger programs. It's really easy to think that something's really not right with how the private keyword works: "If I make a field private, all that does is keep my program from compiling another class tries to use it. But if I just change the private to public, my program builds again! Adding private just broke my program. So why would I ever want to make a field private?"
The reason you want to use private is because sometimes you want your class to hide information from the rest of the program. It's the whole idea behind encapsulation (which is what chapter 5 of Head First C# is all about). A lot of people find encapsulation a little odd the first time they come across it because the idea of hiding one class's fields, properties or methods from another class is a little counterintuitive. But there are some very good reasons that you'll want to think about what information in your class you want to expose to the rest of the program.
There are a few really good reasons to use encapsulation. It makes your classes:
- Easy to use: You already know that classes use fields to keep track of their state. And a lot of them use methods to keep those fields up to date--methods that no other class will ever call. It's pretty common to have a class that has fields, methods and properties that will never be called by any other class. If you make those members private, then they won't pop up in the IntelliSense window later when you need to use that class.
- Easy to maintain: In chapter 5, we uncovered a nasty little bug in a program that happened because the form accessed a field directly rather than using a method to set it. If that field had been private, we would have avoided that bug.
- Flexible: A lot of times, you'll want to go back and add features to a program you wrote a while ago. If your classes are well-encapsulated, then you'll know exactly how to use them later on.
Take a minute and think about it. Can you come up with any ideas about how building a poorly encapsulated class now can make your programs harder to modify later?
Lousy encapsulation today can give you headaches tomorrow
If you read Head First C#, then you should remember Mike's street navigation program from chapter 3. Well, now Mike joined a geocaching team, and he thinks his navigator will give him an edge. But it's been a while since he's worked on it, and now he's run into a little trouble. Mike's navigator program has a Route class that stores a single route between two points. But he's running into all sorts of bugs because he can't seem to figure out how it's supposed to be used! Here's what happened when Mike tried to go back to his navigator and modify the code:
- Mike set the StartPoint property to the GPS coordinates of his home and the EndPoint property to the coordinates of his office and checked the Length property. It said the length was 15.3. When he called the GetRouteLength() method it returned 0.
- He set the SetStartPoint() property to set the start point the coordinates of his home and uses the SetEndPoint() property to set the end point to his office. The GetRouteLength() method returned 9.51, and the Length propety contained 5.91.
- When he tried using the StartPoint property to set the starting point and using the SetEndPoint() method to set the ending point, GetRouteLength() always returned 0 and the Length property always contained 0.
- When he tried using the SetStartPoint() method to set the starting point and the EndPoint property to set the ending point, the Length property contained 0, and the GetRouteLength() method caused the program to crash with an error that said something about not being able to divide by zero.
Mike ran into a classic case of lousy encapsulation. Here's a little exercise that can help you figure this all out.
Here's the Route object from Mike's navigator program. Which properties or methods would you make private in order to make it easier to use?
There are lots of ways to solve this problem, they're all potentially correct! Take a minute and think about which the one you think is best. There's an "a-ha" moment buried in here, and it'll help you understand how private works.
Think of an object as a black box.
Sometimes you'll hear a programmer refer to an object as a "black box" when talking about objects, and that's a pretty good way of thinking about them. When you call that object's methods, you don't really care how that method works--at least, not right now. All you care about is that it takes the inputs you gave it and does the right thing.
Mike's example really shows you how encapsulation with private and public can help. Back in chapter 3, Mike was thinking about how to build his navigator. That's when he really cared about how the Route object worked. But that was a while ago. Since then, he got his navigator working, and he's been using it for a long time. He knows it works well enough to be really useful for his geocaching team. Now he wants to reuse his Route object.
If only Mike had thought about encapsulation when he originally built his Route object. If he had, then it wouldn't be giving him a headache today! Right now, Mike just wants to think about his Route object as a black box. He wants to feed his coordinates into it and get a length out of it. He doesn't want to think about how the Route calculates that length... at least, not right now.
If you encapsulate your classes well today, that makes them a lot easier to reuse tomorrow.
A well-encapsulated class does exactly the same thing as one that has poor encapsulation
The difference is that the well-encapsulated one is built in a way that prevents bugs and is easier to use. It's easy to take a well-encapsulated class and turn it into a poorly encapsulated class: do a search-and-replace to change every occurrence of private to public. And that's a funny thing about the private keyword: you can generally take any program and do that search-and-replace, and it will still compile and work in exactly the same way. That's one reason that encapsulation is difficult for some programmers to understand.
A lot of developers start thinking about this once they've rounded the corner and started to really "get" how classes and objects work. If you're a developer like that, then, everything you've learned so far has been about making programs do things--perform certain behaviors. Encapsulation is a little different. It doesn't change the way your program behaves. It's more about the "chess game" side of programming: by hiding certain information in your classes when you design and build them, you set up a strategy for how they'll interact later. The better the strategy, the more flexible and maintainable your programs will be, and the more bugs you'll avoid.
And just like chess, there are an almost unlimited number of possible encapsulation strategies.
You can read more of Andrew's posts at Building Better Software.