Search - Articles - Dev Forums - Favorites - Member Login  
DevASP.NET for ASP.NET, VB.NET, XML and C# (C-Sharp) Developers Thursday, July 29, 2010

Dev Articles
Search Directory
ASP.NET
VB.Net
C-Sharp
SQL Server
 

A Sample Chapter from C# Programmers Reference



User-defined reference types

The C# language lets us create our own user-defined classes, as well as arrays, delegates, and interfaces. Indeed, most programming work will probably involve defining new reference types. User-defined reference types can be written to do almost anything, and can make use of any combination of
built-in types.

Determining a type

There are two ways to determine the type of a variable. We can use the GetType() method defined in the Object class. This method returns a Type object representing the type of a variable. Alternatively, we can use the typeof operator that does the same thing. The typeof operator takes as its argument either the fully qualified name of a type or a type alias.

 

Example: Determining a type

 

In this simple example, the TypeDemo class defines a method named DoStuff(). This method takes an object argument. Inside the DoStuff() method, the GetType() method is called to return a Type object corresponding to the input argument, and the typeof operator is used to return a Type object corresponding to the String class. The two Type objects are compared.

 


 

 

The DoStuff() method is called twice. The first time the method is passed a double as an argument. The second time the method is called it is passed a string:

 

using System;

 

public class TypeDemo

{

  public static void Main()

  {

    DoStuff(32.45);

    DoStuff("Hello");

  }

 

  public static void DoStuff(object obj)

  {

    if (obj.GetType() ==  typeof(string))

      Console.WriteLine("Argument was a string");

    else

      Console.WriteLine("Argument was not a string");

  }

}

 

Output:

 

Argument was not a string

Argument was a string

 

In this example, we used the alias for the String class. We could also have used the syntax typeof(System.String). Determining an object's type is an important element of reflection. See Chapter 25 for more details on reflection, and for other examples of using GetType() and typeof.

 

Casting

When assembling the classes and methods provided by the .NET Framework into your own application, it may be necessary to convert one data type to another. For instance, consider the following code that uses a FileStream object to read data from a file. The Read() method reads a specified number of bytes from the input stream and places them into a byte[] array. The Length property of the FileStream is used to designate how many bytes to read:

 

FileStream fs = File.Create("data.inp");

byte[] buf2 = new byte[fs.Length];

int k = fs.Read(buf2, 0, fs.Length);

 

This code will not compile. The problem is that the Read() method takes an int as its third argument, but the Length property returns a long. The system attempts to perform an implicit conversion from long to int, but this is not allowed because it is a narrowing conversion (64-bit to 32-bit). If we were trying to convert from an int to a long, this would be a widening conversion, and could be done implicitly.


The solution is to cast the long explicitly to an int. This is done by placing the desired cast type inside parentheses before the value or object to be cast. In our previous example, it would look like this:

 

if (fs.Length <= int.MaxValue)

  int k = fs.Read(buf2, 0, (int)fs.Length);

 

Because narrowing conversions can result in loss of data, we check that the actual value will fit in an int before making the conversion. An alternative approach would be to use the checked operator, which will cause an error to be thrown if the conversion would result in loss of data. The checked operator is covered in Chapter 3.

 

Casting can be performed on both value and reference types. As was previously noted, some casts will be performed implicitly by the compiler. These are widening conversions, going from a smaller data type to a larger one. The implicit conversions supported by C# are:

 

From

To

byte

decimal, double, float, int, long, short, uint, ulong, ushort

char

decimal, double, float, int, long, uint, ulong, ushort

float

double

int

decimal, double, float, long

long

decimal, double, float

sbyte

decimal, double, float, int, long, short

short

decimal, double, float, int, long

uint

decimal, double, float, long, ulong

ulong

decimal, double, float

ushort

decimal, double, float, int, long, uint, ulong

 

Any conversion not listed in the above table must be performed explicitly using a cast. Note that we cannot implicitly or explicitly cast a bool into another type. We must also be careful when performing a narrowing conversion. For example, if we try to cast a long to an int and the value of the long is greater than the maximum value of an int, the statement will compile, but the cast will not be properly performed. Using the checked operator can help detect conversion overflows. We will look in more detail at casting between reference types in Chapter 7, including defining custom casts for class types.

Boxing and unboxing

Both value types and reference types are derived from the Object class. This means that any method that takes an object argument, for instance, can be passed a value type. Similarly, a value type can call an Object class method:

 

int j = 4;

string str = j.ToString();

 

What is happening here is another example of type casting. If you recall, a value-type variable contains data stored on the stack. You might wonder how such a variable could call a reference-type method. The answer is that the value-type variable is implicitly cast into a reference type in a process called boxing. Conceptually, this is achieved by creating a temporary reference-type "box" corresponding to the value type (although the exact process may vary due to compiler optimizations). This is what happens in IL:


 

IL_0000:  ldc.i4.4         // Load the int 4 onto the stack

IL_0001:  stloc.0          // Pop the value off the stack and into V_0

IL_0002:  ldloca.s   V_0   // Push the address of variable V_0

                           // onto the stack

 

// Call Int32::ToString()

IL_0004:  call       instance string [mscorlib]System.Int32::ToString()

 

The key instruction here is ldloca.sV_0, which loads a managed pointer to the V_0 variable – it is this managed pointer, not the value itself, against which the ToString() method is called.

 

We can also explicitly box a value following the normal casting syntax:

 

int j = 4;

object obj = (object)j;

 

We can convert a previously boxed variable back into a value type using the same casting syntax:

 

int k = (int)obj;

 

There are several limitations on unboxing. You can only unbox a variable that has previously been explicitly boxed. The normal casting limitations also apply. For instance, if we box a long into an object, we can't unbox the object into an int, although we can of course explicitly cast the long to an int once we've unboxed it:

 

long l = 1000;

object o = (object)l;

int i = (int)((long)o);

Summary

We've taken a look into some of the core features of C# in this chapter. The format and behavior of types in .NET is defined by the Common Type System (CTS). Not all types defined by the CTS are available to all .NET languages, so the Common Language Infrastructure defines a subset of the CTS, called the Common Language Specification, which specifies the types that all .NET languages must support. The types available when working with C# can be either value or reference types, and this distinction points to the memory allocation system under the hood.

 

In this chapter we've looked at:

 

q       The Common Type System

q       The .NET Framework type hierarchy

q       The differences between value and reference types

q       The basic categories present in the type hierarchy, as well as the predefined value and
reference types

q       How one type can be converted, or cast, into another type

q       How value types can be "boxed" into reference types



 

Copyright and Authorship Notice

This chapter extract is taken from "C# Programmers Reference" by Grant Palmer published by Wrox Press Limited in April 2002; ISBN 1861006306; copyright © Wrox Press Limited 2002; all rights reserved. No part of this chapter may be reproduced, stored in a retrieval system or transmitted in any form or by any means -- electronic, electrostatic, mechanical, photocopying, recording or otherwise -- without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

DevASP.Net - Disclaimer - Privacy
© 2002-2010 DevASP.net