Other Generic Framework Types


In addition to the System.Collections.Generic namespace, the .NET Framework has other uses for generic types. The structs and delegates discussed here are all in the System namespace and serve different purposes.

This section discusses the following:

  • The struct Nullable<T>

  • The delegate EventHandler<TEventArgs>

  • The struct ArraySegment<T>

Nullable<T>

A number in a database and a number in a programming language have an important difference in their characteristics, as a number in the database can be null. A number in C# cannot be null. Int32 is a struct, and because structs are implemented as value types, they cannot be null.

The problem doesn’t exist only with databases but also with mapping XML data to .NET types.

This difference often causes headaches and lot of additional work to map the data. One solution is to map numbers from databases and XML files to reference types, because reference types can have a null value. However, this also means additional overhead during runtime.

With the structure Nullable<T> this can be easily resolved. In the example, Nullable<T> is instantiated with Nullable<int>. The variable x can now be used like an int, assigning values and using operators to do some calculation. This behavior is made possible by casting operators of the Nullable<T> type. However, x can also be null. The Nullable<T> properties HasValue and Value can check if there is a value, and the value can be accessed:

  Nullable<int> x; x = 4; x += 3; if (x.HasValue) {    int y = x.Value; } x = null; 

Because nullable types are used very often, C# has a special syntax for defining variables of this type. Instead of using the syntax with the generic structure, the ? operator can be used. In the following example, the variables x1 and x2 both are instances of a nullable int type:

  Nullable<int> x1; int? x2; 

A nullable type can be compared with null and numbers as shown. Here, the value of x is compared with null, and if it is not null, it is compared with a value smaller than 0:

 int? x = GetNullableType(); Please use brackets here. Good coding standards. [CN] can do it here if (x == null) {    Console.WriteLine("x is null"); } else if (x < 0) {    Console.WriteLine("x is smaller than 0"); } 

Nullable types can also be used with arithmetic operators. The variable x3 is the sum of the variables x1 and x2. If any of the nullable types has a null value, the result is null:

  int? x1 = GetNullableType(); int? x2 = GetNullableType(); int? x3 = x1 + x2; 

Non-nullable types can be converted to nullable types. With the conversion from a non-nullable type to a nullable type, an implicit conversion is possible where casting is not required. This conversion always succeeds:

  int y1 = 4; int? x1 = y1; 

The other way around, the conversion from a nullable type to a non-nullable type, can fail. If the nullable type has a null value and the null value is assigned to a non-nullable type, an exception of type InvalidOperationException is thrown. That’s the reason the cast operator is required to do an explicit conversion:

  int? x1 = GetNullableType(); int y1 = (int)x1; 

Instead of doing an explicit cast, it is also possible to convert a nullable type to a non-nullable type with the coalescing operator. The coalescing operator has the syntax ?? to define a default value for the conversion in case the nullable type has a value of null. Here, y1 gets the value 0 if x1 is null:

 int? x1 = GetNullableType(); int y1 = x1 ?? 0; 

EventHandler<TEventArgs>

With Windows Forms and Web applications, delegates for many different event handlers are defined. Some of the event handlers are listed here:

  public sealed delegate void EventHandler(object sender, EventArgs e); public sealed delegate void PaintEventHandler(object sender, PaintEventArgs e); public sealed delegate void MouseEventHandler(object sender, MouseEventArgs e); 

These delegates have in common that the first argument is always the sender, who was the origin of the event, and the second argument is of a type to contain information specific to the event.

With the new EventHandler<TEventArgs>, it is not necessary to define a new delegate for every event handler. As you can see, the first parameter is defined the same way as before, but the second parameter is a generic type TEventArgs. The where clause specifies that the type for TEventArgs must be derived from the base class EventArgs:

  public sealed delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)    where TEventArgs : EventArgs 

ArraySegment<T>

The struct ArraySegment<T> represents a segment of an array. If parts of an array are needed, a segment can be used. With the struct ArraySegment<T>, the information about the segment (the offset and count) is contained within this structure.

In the example, the variable arr is defined as an int array with eight elements. The variable segment of type ArraySegment<int> is used to represent a segment of the integer array. The segment is initialized with the constructor, where the array is passed together with an offset and an item count. Here, the offset is set to 2, so you start with the third element, and the count is set to 3, so 6 is the last element of the segment.

The array behind the array segment can be accessed with the Array property. ArraySegment<T> also has the properties Offset and Count that indicate the initialized values to define the segment. The for loop is used to iterate through the array segment. The first expression of the for loop is initialized to the offset where the iteration should begin. With the second expression, the count of the element numbers in the segment is used to check if the iteration should stop. Within the for loop, the elements contained by the segment are accessed with the Array property:

  int[] arr = {1, 2, 3, 4, 5, 6, 7, 8}; ArraySegment<int> segment = new ArraySegment<int>(arr, 2, 3); for (int i = segment.Offset; i < segment.Offset + segment.Count; i++) {    Console.WriteLine(segment.Array[i]); } 

With the example so far, you might question the usefulness of the ArraySegment<T> structure. However, the ArraySegment<T> can also be passed as an argument to methods. This way, just a single argument is needed instead of three that define the offset and count in addition to the array.

The method WorkWithSegment() gets an ArraySegment<string> as a parameter. In the implementation of this method, the properties Offset, Count, and Array are used as before:

  void WorkWithSegment(ArraySegment<string> segment) {    for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)    {       Console.WriteLine(segment.Array[i]);    } } 

Important 

It’s important to note that array segments don’t copy the elements of the originating array. Instead, the originating array can be accessed through ArraySegment<T>. If elements of the array segment are changed, the changes can be seen in the original array.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

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