Types

I l @ ve RuBoard

Two kinds of types are supported in C#, value types and reference types. The difference between these two kinds of types is in the way the variables of each type behave.

Value Types

Unlike reference types, variables of value types contain the actual data they represent. Value types consist of predefined types, enums, and structs.

Predefined Types

Predefined types are shorthand for system types found in the .NET platform. Table D.3 outlines each of the value types found in C# as well as the system-provided type to which it corresponds.

Table D.3. Predefined Value Types in C#
Type Name Category Description
object System.Object The absolute base class of all other types
string System.String Unicode character sequence
sbyte System.SByte 8-bit signed integral type
short System.Int16 16-bit signed integral type
int System.Int32 32-bit signed integral type
long System.Int64 64-bit signed integral type
byte System.Byte 8-bit unsigned integral type
ushort System.UInt16 16-bit unsigned integral type
uint System.UInt32 32-bit unsigned integral type
ulong System.UInt64 64-bit unsigned integral type
float System.Single Single-precision floating point type
double System.Double Double-precision floating point type
bool System.Boolean Boolean type containing either true or false
char System.Char Containing value is one Unicode character
Enums

A second kind of value type is the enum , which is a user -defined type name for a group of related constants. This type is often used when a runtime decision is made from a fixed number of choices created at compile time.

  enum Material {     Pine,     Oak,     Maple,     Mahogany } 
Structs

Structs are value types that share many similarities with classes. Like classes, struct definitions can include constants, fields, methods , events, operators, constructors, destructors, and even nested type declarations. See the definition of classes for a more complete definition of these items.

When used strategically, a struct can often boost performance because its values are stored on the stack.

  struct Point {     public int x, y, z;     public Student(int x, int y, int z)     {         this.x = x;         this.y = y;         this.z = z;     } } 

We can use the struct in this way:

  Point[] graph = new Point[1000]; for (int a = 0; a < 1000; a++){     graph[a] = new Point(a, a, a + a); } 

By using a struct instead of a class, the calling program instantiates just one object for an array of our Point struct instead of creating 1001 new objects as it would have if we had used a class declaration.

Conversions

As mentioned before, C# includes a number of predefined value types. These types also have predefined conversions between them. These conversions are either implicit or explicit.

Implicit Conversions

Implicit conversions are those that may be performed without loss of data and without careful observation, for instance, from int to long . Because this conversion requires no loss of data, it never fails.

Explicit Conversions

Unlike implicit conversions, explicit conversions may result in loss of data and must be handled by using a cast expression:

  (int)MyLongVariable 

Reference Types

Reference types are types whose variables store only references to the actual objects they represent. Because of this, operations made on one reference type variable may affect another reference type variable if it references the same object. Reference types include arrays, classes, interfaces, and delegates.

Array Types

C# supports both single-dimensional and multidimensional arrays. Array types are reference types, so array declarations merely set aside space for the array. Array instances are actually created by initializing the array with array creation expressions:

  int[] arr; // declare single-dimension array arr = new int[] { 10, 20, 30} ; // initialize the array int[,] arr2; // declare multi-dimension array arr2 = new int[,] { 10, 20, 30} , { 30, 40, 50} ; // initialize the array 

Also, as with other variables, we can declare the array and initialize it in a single step:

  int[] arr = new int[] { 10, 20, 30} ; // initialize the array int[,] arr2 = new int[,] { 10, 20, 30} , { 30, 40, 50} ; // initialize the array 

Note that the preceding arrays are rectangular arrays because their shape is known. However, C# also supports jagged arrays, also known as "arrays of arrays":

  int[][] jagged = new int[5][]; // declare single-dimension array 
Classes

Class declarations allow the programmer to create new reference types and to help employ the OOP principle of data hiding. Class declarations can include any of the following, which are defined later in the appendix: fields, constants, properties, methods, indexers, operators, events, constructors, destructors, and nested type declarations.

Member Access

Each member defined in the class declaration is accessible to one of five possible degrees. Table D.4 outlines the five degrees of accessibility of class members .

Table D.4. Degrees of accessibility of class members.
Degree Definition
public Unlimited access
protected internal Access limited to the current program or types derived from the containing class
protected Access limited to the containing class or types derived from the containing class
internal Access limited to this program
private Access limited to the containing type
Constants

A constant is a member that represents a value computed at compile time that does not change. A simple class with two constants might look like this:

  class Test {     public const int x = 1;     public const int y = x + 2; } 

Note that constant values can depend upon other constants as long as no circular references are created. With the appropriate access level, constants can be accessed via dot notation as can any other member, like this:

  int A = Test.x; 
Fields

A field member is a variable associated with an object or class. Fields can be declared in the following fashion:

  class Student {     public string FirstName;     public string LastName;     public static readonly int HomeRoom;     public Student(string First, string Last, int Room)     {         FirstName = First;         LastName = Last;         HomeRoom = Room;     } } 

In this example, we have created FirstName and LastName , two instance fields, or variables for which storage is allocated with each instance of the class that is created. We have also created a third field, this time a static field by using the static keyword. In doing so, we have created storage for the HomeRoom field in only the class level, so all instances of the Student class will share this field. To prevent unexpected results by inadvertently overwriting data, we have used the readonly keyword, which allows the field to be changed only during its declaration or a constructor of its containing class.

Properties

Like fields, properties are named members with associated types. However, properties do not designate storage locations for their associated values. Instead, they define accessors that denote the statements to execute to read or write their values. A simple class with a property declaration would resemble the following:

  public class PhoneNumber {     private string m_PhoneNumber;     public string Text     {         get         {             return m_PhoneNumber;         }         set         {             m_PhoneNumber = FormatPhone(value);         }     }     public PhoneNumber(string Phone)     {         m_PhoneNumber = FormatPhone(Phone);     } ... } 

In this skeleton example of a phone number formatting class, we define both get and set accessors for the Text property. The get accessor is called when the Text property is read and, conversely, the set accessor is called when the property's value is changed. Note the implicit value parameter of the set accessor.

We can use this class and its properties in the following manner:

  PhoneNumber pn = new PhoneNumber("8675309"); //constructor formats TextBox1.Text = pn.Text; // get accessor returns formatted number pn.Text = TextBox1.Text; // set accessor, number is formatted and stored 
Methods

A method is a member that implements an action that can be performed by an object or class. Methods have a signature that consists of the name of the method and the number, types, and modifiers of the list of parameters. Methods may have a return type and value or may be void. Like fields, methods may be static methods (accessed through the class) or instance methods (which are accessed through instances of the class). We declare methods in this way:

  public class SportsTicker {     public void Start()     {         //implementation goes here     }     public static void Reset()     {         //implementation goes here     }     public GameInfo GetScoreByTeam(string Team)     {         //implementation goes here     } } 

In this framework example, we've defined three methods. Start is an instance method with no return value, which initiates scrolling on our sports ticker. Reset is a static method, which returns the ticker to its original state. Finally, GetScoreByTeam is an instance method, which returns a GameInfo class (defined separately) containing all the game information for the given team on the current day.

One of the more powerful features of C# that C and C++ developers should be familiar with is that of overloading. Methods may be overloaded, meaning they share the same name but have unique signatures. This is a useful means of providing computation on different kinds of parameters while avoiding creating unique method names for a variety of different input combinations. Consider the following example:

  class Shape {     public int Height, Width;     public int Perimeter()     {         return ((Height * 2) + (Width * 2));     }     public int Perimeter(int length)     {         // assume square         return (length * 4);     }     public int Perimeter(int length, int width)     {         // assume rectangle         return ((length * 2) + (width * 2));     }     public int Perimeter(int[] sides)     {         // unknown shape, calculate         int p = 0;         for (int x = 0; x < sides.length; x++)         {             p += sides[x];         }         return p;     } } 

In this example, we are able to calculate the perimeter of a variety of shapes using the overloaded method Perimeter .

Events

An event is a special member that enables a class to provide notifications to the calling program. Events are declared much like fields with an added event keyword as follows :

  class TestClass {     public event MouseOverHandler MouseOver; } 

The declaration type is always that of a delegate, in this case ClickHandler , which we define as follows:

  public delegate void MouseOverHandler(object sender, System.EventArgs e); 

Delegates and their other uses are discussed later in this reference.

To hook up the event handler for our event, we use the += operator from the calling program:

  public class TestForm {     public TestForm()     {         TestClass1.MouseOver += new    MouseOverHandler(TestClass1_MouseOver);     }     TestClass TestClass1 = new TestClass();     void TestClass1_MouseOver(object sender, EventArgs e)     {         Console.WriteLine("Mouseover!");     }     public void RemoveHandler()     {         // removes the handler         TestClass1.MouseOver -= new    MouseOverHandler(TestClass1_MouseOver);     } } 
Operators

An operator is a member that defines the meaning of an expression operator that can be applied to instances of the class. Table D.5 outlines the forms of operators.

Table D.5. Class Member Operators
Operator Form Operators Implementation
Unary + , - , ! , ~ Takes single parameter of same type as containing type and returns any type
Unary ++ or -- Takes single parameter of same type as containing type and returns same type as containing type
Unary true or false Takes single parameter of same type as containing type and returns type
boolBinary == , != , > , < , >= , <= Takes two parameters, at least one of same type as containing type and returns any type
Conversion N/A Converts from source type to target type, one of which must be of same type as containing type

An example of a unary operator using the + operator would be declared as follows:

  public static TestClass operator+(TestClass x, TestClass y) {     return new TestClass(x.value + y.value); } 

Of course, implementation within the operator is up to the programmer, but additional unary operators would resemble the preceding example. Although a binary operator may return any type, the most often used return type is that of bool . An example of such an operator follows:

  public static bool TestClass operator==(TestClass x, TestClass y) {     return  x.value ==  y.value; } 

The third type of operator is the conversion operator. This type of operator allows the programmer to create user-defined conversions from one type to another. A conversion between two types is valid if

  • the source type and the target types differ

  • either the source type or the target types is of type declaring the conversion

  • neither the source type nor the target type is of type object or an interface type

  • the source type is not a base class of the target type and the target type is not a base class of the source type

After making sure these conditions are met, we must determine whether the conversion must be implicit or explicit. As a rule, implicit conversions are designed to never throw exceptions or lose data. This conversion lends itself to scenarios where data is being converted from a subset type to a super type, as in from char to string . Conversely, explicit conversions are designed to handle situations where loss of data could occur. Conversion operators are defined in the following manner:

  public static implicit operator MyChar(String s) {     return  s.value; } public static explicit operator String(MyChar c) {     return  new MyChar(c); } 
Indexers

An indexer is a member that enables for...each traversal much like an array. Indexer declarations resemble property declarations except that they are nameless and include indexing parameters. If we built a Bucket class to hold a set of Shape objects we created earlier, we could do so in the following way:

  class Bucket {     Shape[] shapes;     int length;     public Bucket(int length)     {         if (length < 0) throw new ArgumentException();         shapes = new Shape[((length  1)];         this.length = length;     }     public int Length     {         get         {             return length;         }     }     public Shape this[int index]     {         get         {             if (index < 0  index >= length)             {                 throw new IndexOutOfRangeException();             }             return (shapes[index]);         }         set         {             if (index < 0  index >= length)             {                 throw new IndexOutOfRangeException();             }             shapes[index] = value;         }     } } 

This allows us to traverse the class in the same way we do an array:

  Bucket Shapes(5); for (int x = 0; x < 20; x++) {     Shapes[x].Height = x;     Shapes[x].Width = x;     Console.WriteLine(Shapes[x].Perimeter()); } 
Instance Constructors

Constructors are members that provide any initialization operations for an instance of a class. Similar in many ways to methods, constructors may be overloaded as well to provide alternate ways of instantiating a class. Constructors have the same name as the class and have no return type. The following example is an extension of our Shape class to include two constructors to allow initialization of the Height and Width fields upon instantiation.

  class Shape {     public int Height, Width;     public Shape(int h, int w)     {         Height = h;         Width = w;     }     public Shape(int h)     {         // assume square         Height = h;         Width = h;     }     public int Perimeter()     {         return ((Height * 2) + (Width * 2));     }     public int Perimeter(int length)     {         // assume square         return (length * 4);     }     public int Perimeter(int length, int width)     {         // assume rectangle         return ((length * 2) + (width * 2));     }     public int Perimeter(int[] sides)     {         // unknown shape, calculate         int p = 0;         for (int x = 0; x < sides.length; x++)         {             p += sides[x];         }         return p;     } } 

Because we had no constructors in our class beforehand, you might have guessed that the use of constructors is optional. If no constructor is provided, the compiler automatically generates an empty constructor with no parameters.

Destructors

A destructor is a member that performs the actions required to terminate an instance of a class. Destructors have no accessibility level, take no parameters, and cannot be called explicitly. Instead, destructors are called implicitly by the system during regular garbage collection. Destructors, like constructors, have the same name as the class and are created in this way:

  ~Shape(){     //add any clean-up code here } 
Static Constructors

Static constructors perform the required actions to initialize a class the same way as instance constructors initialize an instance of a class. However, like destructors, static constructors have no accessibility level, take no parameters, and cannot be called explicitly. A static constructor looks like this:

  Static Shape() {     // implementation code } 
Inheritance

Inheritance is the mechanism that allows one type to derive from another. Classes support single inheritance and all classes ultimately derive from the object type. Consider a generic form of our Shape class once again:

  class Shape {     public int Perimeter(int[] sides)     {         // unknown shape, calculate         int p = 0;         for (int x = 0; x < sides.length; x++)         {             p += sides[x];         }         return p;     } } 

Now let's derive a new type from this class:

  class Rectangle:Shape {     public Area(int h, int w)     {         return h * w;     } } 

Our new class inherits the Perimeter method from Shape while adding its own Area method. However, inheritance doesn't have to be verbatim. By adding the virtual keyword to a method, property, or indexer in the base class, we can override its implementation in the derived class:

  class Shape {     public virtual int Perimeter(int[] sides)     {         // unknown shape, calculate         int p = 0;         for (int x = 0; x < sides.length; x++)         {             p += sides[x];         }         return p;     } } 

Now let's derive a new type from this class and override the Perimeter method:

  class Square:Shape {     public override int Perimeter(int[] sides)     {         return (sides[x] * 4);     }     public Area(int h)     {         return h * h;     } } 

Often, it may be prudent to design the base class with no implementation details and stipulate that the derived class provides its own implementation. This is accomplished by marking the class as abstract , as follows:

  abstract class Shape {     public abstract int Perimeter()     {     } } 

Our derived class is created in the same manner as in the previous example:

  class Square:Shape {     public override int Perimeter()     {         return (sides[x] * 4);     }     public Area(int h)     {         return h * h;     } } 
Interfaces

Interfaces are contracts that a class (or struct) implementing the interface must adhere to. Interface members may include methods, properties, indexers, and events and interfaces support multiple inheritance. They contain no implementation details because that is left to the implementing class or struct:

  interface IValidation {     void Validate(string text); } class PhoneNumber:IValidation {     void Validate(string text)     {         // validation code goes here     } } 
Delegates

Delegates are the C# answer to function pointers in C++. However, delegates are type-safe and secure. Delegates derive from the System.Delegate and encapsulate a callable entity, either a method on an instance class or a static method on a class. A delegate can reference any object as long as the method's signature matches its own signature. Delegates are declared much like methods; after all, they are essentially just method signatures:

  delegate void MyDelegate(); 

After the delegate is declared, it must be instantiated :

 class MyClass {     void TestMethod()     {         Console.WriteLine("Calling MyClass.TestMethod");     }     static void TestDelegateCall(MyDelegate d)     {         d(); // calls TestMethod() anonymously     } } 
I l @ ve RuBoard


Asp. Net. By Example
ASP.NET by Example
ISBN: 0789725622
EAN: 2147483647
Year: 2001
Pages: 154

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net