Type Basics

A type defines the blueprint for a value. In our example, we used two literals of type int with values 12 and 30. We also declared a variable of type int whose name was x.

A variable denotes a storage location that can contain different values over time. In contrast, a constant always represents the same value (more on this later).

All values in C# are an instance of a specific type. The meaning of a value, and the set of possible values a variable can have, is determined by its type.

Predefined Type Examples

Predefined types (also called built-in types) are types that are specially supported by the compiler. The int type is a predefined type for representing the set of integers that fit into 32 bits of memory, from –231 to 231–1. We can perform functions such as arithmetic with instances of the int type as follows:

int x = 12 * 30;

Another predefined C# type is string. The string type represents a sequence of characters, such as “.NET” or “http://oreilly.com”. We can work with strings by calling functions on them as follows:

string message = "Hello world";
string upperMessage = message.ToUpper();
Console.WriteLine (upperMessage);      // HELLO WORLD

int x = 2012;
message = message + x.ToString();
Console.WriteLine (message);         // Hello world2012

The predefined bool type has exactly two possible values: true and false. The bool type is commonly used to conditionally branch execution flow with an if statement. For example:

bool simpleVar = false;
if (simpleVar)
  Console.WriteLine ("This will not print");

int x = 5000;
bool lessThanAMile = x < 5280;
if (lessThanAMile)
  Console.WriteLine ("This will print");

Note

The System namespace in the .NET Framework contains many important types that are not predefined by C# (e.g., DateTime).

Custom Type Examples

Just as we can build complex functions from simple functions, we can build complex types from primitive types. In this example, we will define a custom type named UnitConverter—a class that serves as a blueprint for unit conversions:

using System;

public class UnitConverter
{
  int ratio;                             // Field

  public UnitConverter (int unitRatio)   // Constructor
  {
    ratio = unitRatio;
  }

  public int Convert (int unit)          // Method
  {
    return unit * ratio;
  }
}

class Test
{
  static void Main()
  {
    UnitConverter feetToInches = new UnitConverter(12);
    UnitConverter milesToFeet = new UnitConverter(5280);

    Console.Write (feetToInches.Convert(30));   // 360
    Console.Write (feetToInches.Convert(100));  // 1200
    Console.Write (feetToInches.Convert
                    (milesToFeet.Convert(1)));  // 63360
  }
}

Members of a type

A type contains data members and function members. The data member of UnitConverter is the field called ratio. The function members of UnitConverter are the Convert method and the UnitConverter’s constructor.

Symmetry of predefined types and custom types

A beautiful aspect of C# is that predefined types and custom types have few differences. The predefined int type serves as a blueprint for integers. It holds data—32 bits—and provides function members that use that data, such as ToString. Similarly, our custom UnitConverter type acts as a blueprint for unit conversions. It holds data—the ratio—and provides function members to use that data.

Constructors and instantiation

Data is created by instantiating a type. Predefined types can be instantiated simply by using a literal such as 12 or "Hello, world".

The new operator creates instances of a custom type. We started our Main method by creating two instances of the UnitConverter type. Immediately after the new operator instantiates an object, the object’s constructor is called to perform initialization. A constructor is defined like a method, except that the method name and return type are reduced to the name of the enclosing type:

public UnitConverter (int unitRatio)   // Constructor
{
  ratio = unitRatio;
}

Instance versus static members

The data members and function members that operate on the instance of the type are called instance members. The UnitConverter’s Convert method and the int’s ToString method are examples of instance members. By default, members are instance members.

Data members and function members that don’t operate on the instance of the type, but rather on the type itself, must be marked as static. The Test.Main and Console.WriteLine methods are static methods. The Console class is actually a static class, which means all its members are static. You never actually create instances of a Console—one console is shared across the whole application.

To contrast instance with static members, the instance field Name pertains to an instance of a particular Panda, whereas Population pertains to the set of all Panda instances:

public class Panda
{
  public string Name;           // Instance field
  public static int Population; // Static field

  public Panda (string n)       // Constructor
  {
    Name = n;                   // Assign instance field
    Population = Population+1;  // Increment static field
  }
}

The following code creates two instances of the Panda, prints their names, and then prints the total population:

Panda p1 = new Panda ("Pan Dee");
Panda p2 = new Panda ("Pan Dah");

Console.WriteLine (p1.Name);      // Pan Dee
Console.WriteLine (p2.Name);      // Pan Dah

Console.WriteLine (Panda.Population);   // 2

The public keyword

The public keyword exposes members to other classes. In this example, if the Name field in Panda was not public, the Test class could not access it. Marking a member public is how a type communicates: “Here is what I want other types to see—everything else is my own private implementation details.” In object-oriented terms, we say that the public members encapsulate the private members of the class.

Conversions

C# can convert between instances of compatible types. A conversion always creates a new value from an existing one. Conversions can be either implicit or explicit: implicit conversions happen automatically whereas explicit conversions require a cast. In the following example, we implicitly convert an int to a long type (which has twice the bitwise capacity of an int) and explicitly cast an int to a short type (which has half the bitwise capacity of an int):

int x = 12345;       // int is a 32-bit integer
long y = x;          // Implicit conversion to 64-bit int
short z = (short)x;  // Explicit conversion to 16-bit int

In general, implicit conversions are allowed when the compiler can guarantee they will always succeed without loss of information. Otherwise, you must perform an explicit cast to convert between compatible types.

Value Types Versus Reference Types

C# types can be divided into value types and reference types.

Value types comprise most built-in types (specifically, all numeric types, the char type, and the bool type) as well as custom struct and enum types. Reference types comprise all class, array, delegate, and interface types.

The fundamental difference between value types and reference types is how they are handled in memory.

Value types

The content of a value-type variable or constant is simply a value. For example, the content of the built-in value type, int, is 32 bits of data.

You can define a custom value type with the struct keyword (see Figure 1-1):

public struct Point { public int X, Y; }
A value-type instance in memory

Figure 1-1. A value-type instance in memory

The assignment of a value-type instance always copies the instance. For example:

Point p1 = new Point();
p1.X = 7;

Point p2 = p1;             // Assignment causes copy

Console.WriteLine (p1.X);  // 7
Console.WriteLine (p2.X);  // 7

p1.X = 9;                  // Change p1.X
Console.WriteLine (p1.X);  // 9
Console.WriteLine (p2.X);  // 7

Figure 1-2 shows that p1 and p2 have independent storage.

Assignment copies a value-type instance

Figure 1-2. Assignment copies a value-type instance

Reference types

A reference type is more complex than a value type, having two parts: an object and the reference to that object. The content of a reference-type variable or constant is a reference to an object that contains the value. Here is the Point type from our previous example rewritten as a class (see Figure 1-3):

public class Point { public int X, Y; }
A reference-type instance in memory

Figure 1-3. A reference-type instance in memory

Assigning a reference-type variable copies the reference, not the object instance. This allows multiple variables to refer to the same object—something not ordinarily possible with value types. If we repeat the previous example, but with Point now a class, an operation via p1 affects p2:

Point p1 = new Point();
p1.X = 7;

Point p2 = p1;             // Copies p1 reference

Console.WriteLine (p1.X);  // 7
Console.WriteLine (p2.X);  // 7

p1.X = 9;                  // Change p1.X
Console.WriteLine (p1.X);  // 9
Console.WriteLine (p2.X);  // 9

Figure 1-4 shows that p1 and p2 are two references that point to the same object.

Assignment copies a reference

Figure 1-4. Assignment copies a reference

Null

A reference can be assigned the literal null, indicating that the reference points to no object. Assuming Point is a class:

Point p = null;
Console.WriteLine (p == null);   // True

Accessing a member of a null reference generates a runtime error:

Console.WriteLine (p.X);   // NullReferenceException

In contrast, a value type cannot ordinarily have a null value:

struct Point {...}
...
Point p = null;  // Compile-time error
int x = null;    // Compile-time error

Note

C# has a special construct called nullable types for representing value-type nulls (for more information, see Nullable Types).

Predefined Type Taxonomy

The predefined types in C# are:

Value types
  • Numeric

    • Signed integer (sbyte, short, int, long)

    • Unsigned integer (byte, ushort, uint, ulong)

    • Real number (float, double, decimal)

  • Logical (bool)

  • Character (char)

Reference types
  • String (string)

  • Object (object)

Predefined types in C# alias Framework types in the System namespace. There is only a syntactic difference between these two statements:

int i = 5;
System.Int32 i = 5;

The set of predefined value types excluding decimal are known as primitive types in the Common Language Runtime (CLR). Primitive types are so called because they are supported directly via instructions in compiled code, which usually translates to direct support on the underlying processor.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.166.131