Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Delegate and Interface Contra-variant and

Covariant Generic Type Arguments......................................................... 279

Generic Methods.................................................................................... 281

Generics and Other Members................................................................. 284

Verifiability and Constraints.................................................................... 284

 

Developers who are familiar with object-oriented programming know the benefits it offers. One of the big benefits that make developers extremely productive is code reuse, which is the ability to

derive a class that inherits all of the capabilities of a base class. The derived class can simply override virtual methods or add some new methods to customize the behavior of the base class to meet the developer’s needs. Generics is another mechanism offered by the common language runtime (CLR) and programming languages that provides one more form of code reuse: algorithm reuse.

Basically, one developer defines an algorithm such as sorting, searching, swapping, comparing, or converting. However, the developer defining the algorithm doesn’t specify what data type(s) the

algorithm operates on; the algorithm can be generically applied to objects of different types. Another developer can then use this existing algorithm as long as he or she indicates the specific data type(s) the algorithm should operate on, for example, a sorting algorithm that operates on Int32s, Strings, etc., or a comparing algorithm that operates on DateTimes, Versions, etc.

Most algorithms are encapsulated in a type, and the CLR allows the creation of generic reference types as well as generic value types, but it does not allow the creation of generic enumerated types. In addition, the CLR allows the creation of generic interfaces and generic delegates. Occasionally, a single method can encapsulate a useful algorithm, and therefore, the CLR allows the creation of ge- neric methods that are defined in a reference type, value type, or interface.

Let’s look at a quick example. The Framework Class Library (FCL) defines a generic list algorithm that knows how to manage a set of objects; the data type of these objects is not specified by the generic algorithm. Someone wanting to use the generic list algorithm can specify the exact data type to use with it later.


The FCL class that encapsulates the generic list algorithm is called List<T> (pronounced List of Tee), and this class is defined in the System.Collections.Generic namespace. Here is what this class definition looks like (the code is severely abbreviated).

 

[Serializable]

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable {

 

public List();

public void Add(T item);

public Int32 BinarySearch(T item); public void Clear();

public Boolean Contains(T item); public Int32 IndexOf(T item); public Boolean Remove(T item); public void Sort();



public void Sort(IComparer<T> comparer); public void Sort(Comparison<T> comparison); public T[] ToArray();

 

public Int32 Count { get; }

public T this[Int32 index] { get; set; }

}

 

The programmer who defined the generic List class indicates that it works with an unspecified data type by placing the <T> immediately after the class name. When defining a generic type or method, any variables it specifies for types (such as T) are called type parameters. T is a variable name that can be used in source code anywhere a data type can be used. For example, in the List class definition, you see T being used for method parameters (the Add method accepts a parameter of type

T) and return types (the ToArray method returns a single-dimension array of type T). Another ex- ample is the indexer method (called this in C#). The indexer has a get accessor method that returns a value of type T and a set accessor method that accepts a parameter of type T. Because the T vari- able can be used anywhere that a data type can be specified, it is also possible to use T when defining local variables inside a method or when defining fields inside a type.

       
   
 
 

 

Now that the generic List<T> type has been defined, other developers can use this generic algorithm by specifying the exact data type they would like the algorithm to operate on. When using a generic type or method, the specified data types are referred to as type arguments. For example, a developer might want to work with the List algorithm by specifying a DateTime type argument.


Here is some code that shows this.

 

private static void SomeMethod() {

// Construct a List that operates on DateTime objects List<DateTime> dtList = new List<DateTime>();

 

// Add a DateTime object to the list dtList.Add(DateTime.Now); // No boxing

 

// Add another DateTime object to the list dtList.Add(DateTime.MinValue); // No boxing

 

// Attempt to add a String object to the list dtList.Add("1/1/2004"); // Compile­time error

 

// Extract a DateTime object out of the list DateTime dt = dtList[0]; // No cast required

}

 

Generics provide the following big benefits to developers as exhibited by the code just shown:

 

Source code protectionThe developer using a generic algorithm doesn’t need to have ac- cess to the algorithm’s source code. With C++ templates, however, the algorithm’s source code must be available to the developer who is using the algorithm.

Type safetyWhen a generic algorithm is used with a specific type, the compiler and the CLR understand this and ensure that only objects compatible with the specified data type are used with the algorithm. Attempting to use an object of an incompatible type will result in either

a compiler error or a run-time exception being thrown. In the example, attempting to pass a

String object to the Add method results in the compiler issuing an error.

 

Cleaner codeBecause the compiler enforces type safety, fewer casts are required in your source code, meaning that your code is easier to write and maintain. In the last line of Some­ Method, a developer doesn’t need to use a (DateTime) cast to put the result of the indexer (querying element at index 0) into the dt variable.

Better performanceBefore generics, the way to define a generalized algorithm was to define all of its members to work with the Object data type. If you wanted to use the algo- rithm with value type instances, the CLR had to box the value type instance prior to calling the members of the algorithm. As discussed in Chapter 5, “Primitive, Reference, and Value Types,” boxing causes memory allocations on the managed heap, which causes more frequent garbage collections, which, in turn, hurt an application’s performance. Because a generic algorithm can now be created to work with a specific value type, the instances of the value

type can be passed by value, and the CLR no longer has to do any boxing. In addition, because casts are not necessary (see the previous bullet), the CLR doesn’t have to check the type safety of the attempted cast, and this results in faster code too.


To drive home the performance benefits of generics, I wrote a program that tests the performance of the generic List algorithm against the FCL’s non-generic ArrayList algorithm. In fact, I tested the performance of these two algorithms by using both value type objects and reference type ob- jects. Here is the program itself.

 

using System;

using System.Collections;

using System.Collections.Generic; using System.Diagnostics;

 

public static class Program { public static void Main() {

ValueTypePerfTest(); ReferenceTypePerfTest();

}

 

private static void ValueTypePerfTest() { const Int32 count = 100000000;

 

using (new OperationTimer("List<Int32>")) { List<Int32> l = new List<Int32>();

for (Int32 n = 0; n < count; n++) { l.Add(n); // No boxing

Int32 x = l[n]; // No unboxing

}

l = null; // Make sure this gets garbage collected

}

 

using (new OperationTimer("ArrayList of Int32")) { ArrayList a = new ArrayList();

for (Int32 n = 0; n < count; n++) { a.Add(n); // Boxing

Int32 x = (Int32) a[n]; // Unboxing

}

a = null; // Make sure this gets garbage collected

}

}

 

private static void ReferenceTypePerfTest() { const Int32 count = 100000000;

 

using (new OperationTimer("List<String>")) { List<String> l = new List<String>();

for (Int32 n = 0; n < count; n++) {

l.Add("X"); // Reference copy

String x = l[n]; // Reference copy

}

l = null; // Make sure this gets garbage collected

}

 

using (new OperationTimer("ArrayList of String")) { ArrayList a = new ArrayList();

for (Int32 n = 0; n < count; n++) {

a.Add("X"); // Reference copy

String x = (String) a[n]; // Cast check & reference copy


}

a = null; // Make sure this gets garbage collected

}

}

}

 

// This class is useful for doing operation performance timing internal sealed class OperationTimer : IDisposable {

private Stopwatch m_stopwatch; private String m_text;

private Int32 m_collectionCount;

 

public OperationTimer(String text) { PrepareForOperation();

 

m_text = text;

m_collectionCount = GC.CollectionCount(0);

 

// This should be the last statement in this

// method to keep timing as accurate as possible m_stopwatch = Stopwatch.StartNew();

}

 

public void Dispose() {

Console.WriteLine("{0} (GCs={1,3}) {2}", (m_stopwatch.Elapsed), GC.CollectionCount(0) ­ m_collectionCount, m_text);

}

 

private static void PrepareForOperation() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();

}

}

 

When I compile and run a release build (with optimizations turned on) of this program on my com- puter, I get the following output.

 

00:00:01.6246959 (GCs= 6) List<Int32>

00:00:10.8555008 (GCs=390) ArrayList of Int32

00:00:02.5427847 (GCs= 4) List<String>

00:00:02.7944831 (GCs= 7) ArrayList of String

 

The output here shows that using the generic List algorithm with the Int32 type is much faster than using the non-generic ArrayList algorithm with Int32. In fact, the difference is phenomenal:

1.6 seconds versus almost 11 seconds. That’s ~7 times faster! In addition, using a value type (Int32) with ArrayList causes a lot of boxing operations to occur, which results in 390 garbage collections. Meanwhile, the List algorithm required 6 garbage collections.

The result of the test using reference types is not as momentous. Here we see that the times and number of garbage collections are about the same. So it doesn’t appear that the generic List algo- rithm is of any benefit here. However, keep in mind that when using a generic algorithm, you also get cleaner code and compile-time type safety. So although the performance improvement is not huge, the other benefits you get when using a generic algorithm are usually an improvement.


 

 

 
 

Generics in the Framework Class Library

Certainly, the most obvious use of generics is with collection classes, and the FCL defines several generic collection classes available for your use. Most of these classes can be found in the System. Collections.Generic namespace and the System.Collections.ObjectModel namespace. There are also thread-safe generic collection classes available in the System.Collections.Concurrent namespace. Microsoft recommends that programmers use the generic collection classes and now dis- courages use of the non-generic collection classes for several reasons. First, the non-generic collection classes are not generic, and so you don’t get the type safety, cleaner code, and better performance that you get when you use generic collection classes. Second, the generic classes have a better object model than the non-generic classes. For example, fewer methods are virtual, resulting in better perfor- mance, and new members have been added to the generic collections to provide new functionality.

The collection classes implement many interfaces, and the objects that you place into the col- lections can implement interfaces that the collection classes use for operations such as sorting and searching. The FCL ships with many generic interface definitions so that the benefits of generics can be realized when working with interfaces as well. The commonly used interfaces are contained in the System.Collections.Generic namespace.

The new generic interfaces are not a replacement for the old non-generic interfaces; in many sce- narios, you will have to use both. The reason is backward compatibility. For example, if the List<T> class implemented only the IList<T> interface, no code could consider a List<DateTime> object an IList.

I should also point out that the System.Array class, the base class of all array types, offers many static generic methods, such as AsReadOnly, BinarySearch, ConvertAll, Exists, Find, FindAll, FindIndex, FindLast, FindLastIndex, ForEach, IndexOf, LastIndexOf, Resize, Sort, and TrueForAll. Here are examples showing what some of these methods look like.

 

public abstract class Array : ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable {

 

public static void Sort<T>(T[] array);

public static void Sort<T>(T[] array, IComparer<T> comparer);

 

public static Int32 BinarySearch<T>(T[] array, T value); public static Int32 BinarySearch<T>(T[] array, T value,

IComparer<T> comparer);

...

}


Here is code that demonstrates how to use some of these methods.

 

public static void Main() {

// Create & initialize a byte array

Byte[] byteArray = new Byte[] { 5, 1, 4, 2, 3 };

 

// Call Byte[] sort algorithm Array.Sort<Byte>(byteArray);

 

// Call Byte[] binary search algorithm

Int32 i = Array.BinarySearch<Byte>(byteArray, 1); Console.WriteLine(i); // Displays "0"

}

 

 


Date: 2016-03-03; view: 551


<== previous page | next page ==>
Nbsp;   Explicitly Implementing an Event | Nbsp;   Generics Infrastructure
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.013 sec.)