Beginning ASP.NET 2.0 With CSharp (2006) [eng]
.pdf
Chapter 9
The events of a class provide the user of the class with information about the status. For example, SqlConnection has two useful events: InfoMessage and StateChange. The first is raised (or fired) when SQL Server returns a warning or some informational message, and the second is raised when the State is changed, such as when the connection is closed.
Creating Classes
You can create classes in ASP.NET pages, but if you are creating the class for a special purpose, it’s best to use a separate file and location. The best place for these is in the App_Code directory, a special directory into which class files can be put. Using this directory enables you to keep your class files together, as well as having them automatically compiled by ASP.NET. You look at creating this later, but for now take a look at the structure of a class. The syntax is as follows:
[Accessor] class ClassName
{
// class contents
}
The ClassName can be anything you want, but like variable naming, it’s best to use something sensible. For example, the shopping cart item is a class called CartItem, and the cart itself is called
ShoppingCart.
The Accessor defines the accessibility of the class; that is where the class can be seen from. This can be one of the values detailed in the following table:
Accessor |
Description |
|
|
public |
No access restrictions. |
private |
The class is only accessible from within its declaration context. |
protected |
The class is only accessible from within its own class or derived |
|
classes. |
internal |
The class is only accessible from within the assembly that contains it. |
protected internal |
The class is only accessible from within its own class, a derived |
|
class, or its containing assembly. |
|
|
It’s best not to worry too much about these for the moment. As a general rule, if you create a class that’s going to be used in ASP.NET pages you should use public. So your shopping cart is as follows:
public class ShoppingCart
{
}
328
Code
Constructors
The constructor is a special method that runs when the class is instantiated, and it allows you to set the initial state of the class when it’s created. The constructor is always named the same as the class. For example, the shopping cart with the constructor is highlighted here:
public class ShoppingCart
{
private DateTime _dateCreated; private List<CartItem> _items;
public ShoppingCart()
{
_items = new List<CartItem>; _dateCreated = DateTime.Now;
}
}
What this constructor does is create another object — a new List of CartItem objects. This is what the cart items are stored in — it’s a collection of CartItem objects (you look at this in more detail in the “Generics” section). After that’s created, the initial creation date is set. You could use this ShoppingCart class like so:
ShoppingCart cart = new ShoppingCart();
This would create a new instance, and you’d know that the items collection had also been created, ready for you to add items.
The CartItem class shows another aspect of constructors — overloading:
public class CartItem
{
private double _lineTotal; private double _price; private int _productID;
private string _productImageUrl; private string _productName; private int _quantity;
public CartItem
{
}
public CartItem(int ProductID, string ProductName, string ProductImageUrl, int Quantity, double Price)
{
this._productID = ProductID; this._productName = ProductName; this._productImageUrl = ProductImageUrl; this._quantity = Quantity;
this._price = Price; this._lineTotal = Quantity * Price;
}
}
329
Chapter 9
Here there are two constructors. The first, with no parameters, does nothing, and the second accepts parameters containing the details for an item being bought. Don’t worry too much about the specific syntax of the parameters, because they’re examined in detail during the discussion of methods. The important point to note is that the constructor is overloaded — that is, there are two of them. You can have overloaded constructors (and methods) as long as the signature differs. A signature is what defines the uniqueness of a constructor or method — this includes its name, its return type, and the type and order of the parameters. Because these two constructors have different signatures, they are both allowed.
Having two constructors means the class can be created in two ways. Either with this:
CartItem item = new CartItem();
Or with this:
Cartitem item = new CartItem(1, “Scarf”, “images\scarf.jpg”, 1, 4.95);
The first constructor would create an empty cart item, ready for you to fill in the details, and the second constructor creates a cart item with the details already filled in. These details are passed in as parameters within the parentheses, and these get mapped to the parameters declared in the New method.
Properties
Properties are used to control the characteristics of a class, or to expose to users of the class some internal values. For example, consider the CartItem class — if the first constructor is used, how would the details of the item being bought be set? The variables can’t be accessed directly because they are private, so they can’t be seen outside of the class. Properties are the answer, and these would be created in the following manner:
public class CartItem
{
private double _lineTotal; private double _price; private int _productID;
private string _productImageUrl; private string _productName; private int _quantity;
public int ProductID
{
get
{
return _ProductID;
}
set
{
_productID = value;
}
}
}
330
Code
Break this down and look at the parts. First you have the definition of the property itself, by use of public followed by the data type and the name of the property. So it’s like a variable declaration, but it contains some content as well — public means it’s going to be accessible outside of the class, which is exactly what you want. In fact, it’s the reason for creating the property in the first place, to make the internal variable accessible.
Next comes the bit that allows read access to the property — the get section (often called the getter), which simply returns the value of the internal private variable:
get
{
return _ProductID;
}
get is a special keyword that allows the property to be read. When you access a property like this, the get part of the property is run:
int pid = item.ProductID;
Next is set section. The set (or setter as it’s sometimes called) allows the property to be written to, and the value being written is held in the value variable, which is a special variable that is automatically defined:
set
{
_productID = value;
}
}
To set a property value, you do this:
item.ProductID = 123;
When this is executed, the set part of the property is run. In this case, the value of 123 would be automatically assigned to the value variable before the set part of the property is run. Remember that C# is context-insensitive, meaning that lines can be combined. For example, you might see properties defined like this:
public int ProductID
{
get {return _productID;} set {_productID = value;}
}
Here all parts of the get have been combined onto one line, as have all parts of the set. This has no effect on the code apart from readability.
These forms of defining properties are the same for all properties, with only the name and data type changing. For example, a property for the product name would be:
331
Chapter 9
public string ProductName
{
get
{
return _Productname;
}
set
{
_productName = value;
}
}
With these ProductID and ProductName properties in place, the CartItem could be used like so:
CartItem item = new CartItem();
item.ProductID = 1;
item.ProductName = “The WroxUnited Scarf”
This would create the class and set the properties. When setting these properties the setter part of the property is called, and the value to be set (1 in this case of the ProductID) is passed into the setter as the variable value.
To read from the properties, you could do the following:
IDTextBox.Text = item.ProductID.ToString();
NameTextBox.Text = item.ProductName;
The first line reads from the property, converting to a string, because the Text property of the TextBox is a string type. When the property is accessed, the getter is called, which simply returns the value from the internal variable. For the ProductID, this is an int, but the ProductName property is a string, so no conversion is required.
Read-Only Properties
If you want to provide read-only access to a property, and not allow the user of the class to update the property, you can make it read-only, like so:
public int ProductID
{
get
{
return _ProductID;
}
}
Only the getter section of the property is included, so this automatically becomes a read-only property, and the value cannot be set. This is exactly what the LineTotal property of the CartItem does:
332
Code
public double LineTotal()
{
get
{
return _quantity * _price;
}
}
Here you can see that you don’t even have a private variable. The value returned is simply a calculation.
Because the property is read-only, the value cannot be set, and trying to do so will generate a compile error.
Write-Only Properties
Making a property write-only follows a similar procedure to read-only, except only the setter part of the property is used:
public int ProductID()
{
set
{
_productID = value;
}
}
With this definition, trying to read from the property would result in a compile error.
Properties Versus Public Variables
While you’ve been reading the text on properties, you might have been wondering why they are used. Why not just make the internal variables public? Well, you could easily do this:
public class CartItem
{
public int ProductID; public string ProductName;
public string roductImageUrl; public int Quantity;
public double Price; public double LineTotal;
}
This would work, but is not a good idea because it breaks one of the rules of object orientation — abstraction. (There are other rules of object orientation, but they aren’t pertinent to this particular topic.) This means that you should abstract functionality, thus hiding the inner workings of the class. The reason for working this way is that it allows you to change how the class works internally without changing how it’s used. For example, consider LineTotal, which is the Quantity multiplied by the Price. You have to have some way of calculating the total, and if you use a public variable, then where do you put the calculation? It could be done in the constructor, but what about the blank constructor that just creates an
333
Chapter 9
empty item? Would you have the user of the class calculate the total, or provide some other function to do it? Neither is a good solution.
Abstraction means that you are providing a simple, guaranteed way to access the functionality of the class, and that the user doesn’t have to know about how the class works. You’re using properties to hide the internal storage — those private variables are the internal storage, and the properties are just a way for users of the class to access the internal variables.
Abstracting the internal storage with properties enables you to handle the problem of a line total, because the property accessor does the calculation for you. In fact, using this type of abstraction means you could store the internal state of the CartItem in any way you pleased, without having to change the code that uses CartItem.
There are no strict rules on naming for the private variables that actually store the property values, but an underscore as a prefix is commonly used, as well as a different case. So your property would be LineTotal, whereas the internal private variable is _lineTotal.
Methods
Methods are the actions of a class, what you use to get the class to perform a task. The CartItem class is purely for storage, has no actions, and therefore contains no methods (although technically the constructor is a method). The shopping cart, however, contains methods, because you need to do things like insert, update, and delete items.
Methods fall into two types: those that return a value and those that don’t. The first form is useful for performing an action and then returning some result, perhaps whether the action succeeded or not. The second is useful when you don’t need a value from the action.
The syntax for a method as follows:
[Accessor] DataType FunctionName([parameters])
{
return value
}
The syntax is the same whether or not you are returning a value. If you aren’t returning a value, you use the data type of void,which is a special data type for methods that simply tells ASP.NET that no value is being returned from the method.
Like classes, the Accessor defines the visibility of the method. Use public for methods that are visible from everywhere, and use private for a method that is used only by the class. Methods accept an optional parameter list, much like constructors.(You’ll look at the ins and outs of parameters in more detail soon.)
The difference between a method that returns a value and one that doesn’t is minimal. The first difference, as mentioned, is that you use the void type for a method that doesn’t return a value. Use the shopping cart and examine the methods, starting with deleting items, because that’s the simplest:
public void DeleteItem(int rowID)
{
_items.RemoveAt(rowID); _lastUpdate = DateTime.Now;
}
334
Code
Here the method takes a single parameter — the index of the row to be deleted. This index is then passed into the RemoveAt method of the _items collection, which is the collection that stores the cart items. When the item has been removed, the last update time is set. The method could be called like so:
Profile.Cart.DeleteItem(2);
This would remove the third row. (Remember that arrays and collections start at 0.)
Inserting items into the cart is done with the Insert method:
public void Insert(int ProductID, double Price, int Quantity, string ProductName, string ProductImageUrl)
{
CartItem NewItem = new CartItem(); NewItem.ProductID = ProductID; NewItem.Quantity = Quantity; NewItem.Price = Price; NewItem.ProductName = ProductName; NewItem.ProductImageUrl = ProductImageUrl; _items.Add((CartItem) NewItem); _lastUpdate = DateTime.Now;
}
This routine accepts five parameters, one for each part of the item (the ID, the price, and so on). Within the routine, a new CartItem is created, and the properties are set to the values of the parameters. After all of the parameters are set, the item is added to the _items collection and the time is updated.
One problem with the Insert method is that it could be called multiple times for the same product, which would result in multiple items in the cart. It would be more sensible to see if the item is already in the cart, and if so, simply add one to the quantity. To do this, you need to search through the collection looking for an item with the same ProductID, so a function has been created to do this:
private int ItemIndexOfID(int ProductID)
{
int index = 0;
foreach (CartItem item in _items)
{
if (item.ProductID == ProductID) return index;
index++;
}
return -1;
}
Here the function accepts the ProductID as a parameter and returns an int. Within the function, the _items collection is looped, and if the ProductID of an item matches the supplied ProductID, the return statement is used to return the index. If the loop ends without having found a match, -1 is returned. Notice that this function is marked as private.That’s because it’s not going to be used from outside the class.
335
Chapter 9
The Insert method can now be changed to the following:
public void Insert(int ProductID, double Price, int Quantity, string ProductName, string ProductImageUrl)
{
int ItemIndex = this.ItemIndexOfID(ProductID); if (ItemIndex == -1)
{
CartItem NewItem = new CartItem(); NewItem.ProductID = ProductID; NewItem.Quantity = Quantity; NewItem.Price = Price;
NewItem.ProductName = ProductName; NewItem.ProductImageUrl = ProductImageUrl; _items.Add((CartItem) NewItem);
}
else
{
_items[ItemIndex].Quantity++;
}
_lastUpdate = DateTime.Now;
}
The method still accepts the same parameters, but the first thing it does is call the private ItemIndexOfID to get the index number of the current product. If the index is -1, it didn’t already exist in the collection and is added. If it does exist, the Quantity is increased.
Referencing Internal Variables
There are two ways to access the private internal variables within properties and methods of a class. You can use only the variable name, as shown here:
public string ProductName
{
get
{
return _Productname;
}
set
{
_productName = value;
}
}
Alternatively, you can use an object reference, with the keyword this, which means the current instance of the class. For example:
public string ProductName
{
get
{
return this._Productname;
}
336
Code
set
{
this._productName = value;
}
}
There’s no real difference between these two uses of internal variables, and you’ll see both forms used in help files and examples.
Shared Methods and Properties
With the classes you’ve seen so far, you have to create an instance of them before they can be used. For certain classes, this can be an overhead that’s not really required. For example, consider a class called Tools that provides a range of utility methods, one of which is Log, to log exceptions:
public class Tools
{
public void Log(string ErrorMessage)
{
// log the error
}
}
You could use this class like so:
Tools u = new Tools();
u.Log(“An exception occurred”);
The object instance only exists for the purpose of calling the Log method. There are no properties to set, so it seems a bit of a waste to have to create the instance, especially if it’s only going to be used once. To get around this, you can create shared class members (sometimes called static members). For example:
public class Tools
{
public static void Log(string ErrorMessage)
{
// log the error
}
}
The introduction of the Shared keyword means that a class instance is no longer required, allowing the method to be called like so:
Utils.Log(“An exception occurred”);
When you’re dealing with utility classes, shared methods are extremely useful. You’ll see how the logging features can be implemented in Chapter 15.
337
