Generic Classes Features


Generic Classes’ Features

When creating generic classes, you might need some more C# keywords. For example, it is not possible to assign null to a generic type. In this case, the keyword default can be used. If the generic type does not require the features of the Object class, but you need to invoke some specific methods in the generic class, you can define constraints.

This section discusses the following topics:

  • Default Values

  • Constraints

  • Inheritance

  • Static members

Let’s start this example with a generic document manager. The document manager is used to read and write documents from a queue. Start by creating a new Console project named DocumentManager and add the class DocumentManager<T>. The method AddDocument() adds a document to the queue. The read-only property IsDocumentAvailable returns true if the queue is not empty.

  using System; using System.Collections.Generic; namespace Wrox.ProCSharp.Generics {    public class DocumentManager<T>    {       private readonly Queue<T> documentQueue = new Queue<T>();       public void AddDocument(T doc)       {          lock (this)          {             documentQueue.Enqueue(doc);          }       }       public bool IsDocumentAvailable       {          get { return documentQueue.Count > 0; }       }    } } 

Default Values

Now you add a GetDocument() method to the DocumentManager<T> class. Inside this method the type T should be assigned to null. However, it is not possible to assign null to generic types. The reason is that a generic type can also be instantiated as a value type, and null is allowed only with reference types. To circumvent this problem, you can use the default keyword. With the default keyword, null is assigned to reference types and 0 is assigned to value types.

  public T GetDocument() {   T doc = default(T);   lock (this)   {      doc = documentQueue.Dequeue();   }   return doc; } 

Tip 

The default keyword has multiple meanings depending on the context where it is used. The switch statement uses a default for defining the default case, and with generics the default is used to initialize generic types either to null or 0 depending on if it is a reference or value type.

Constraints

If the generic class needs to invoke some methods from the generic type, you have to add constraints. With the DocumentManager<T>, all the titles of the documents should be displayed in the DisplayAllDocuments() method.

The Document class implements the interface IDocument with the properties Title and Content:

  public interface IDocument {    string Title { get; set; }    string Content { get; set; } } public class Document : IDocument {    public Document()    {    }    public Document(string title, string content)    {       this.title = title;       this.content = content;    }    private string title;    public string Title    {       get { return title; }       set { title = value; }    }    private string content;    public string Content    {       get { return content; }       set { content = value; }    } } 

For displaying the documents with the DocumentManager<T> class, you can cast the type T to the interface IDocument to display the title:

  public void DisplayAllDocuments() {    foreach (T doc in documentQueue)    {       Console.WriteLine((IDocument)doc).Title);    } } 

The problem is that doing a cast results in a runtime exception if the type T does not implement the interface IDocument. Instead, it would be better to define a constraint with the DocumentManager<TDocument> class that the type TDocument must implement the interface IDocument. To clarify the requirement in the name of the generic type, T is changed to TDocument. The where clause defines the requirement to implement the interface IDocument:

 public class DocumentManager<TDocument>     where TDocument : IDocument {

This way you can write the foreach statement in such a way that the type T contains the property Title. You get support from Visual Studio IntelliSense and from the compiler:

 public void DisplayAllDocuments() {     foreach (TDocument doc in documentQueue)     {         Console.WriteLine(doc.Title);     } }

In the Main() method the DocumentManager<T> class is instantiated with the type Document that implements the required interface IDocument. Then new documents are added and displayed, and one of the documents is retrieved:

  static void Main() {    DocumentManager<Document> dm = new DocumentManager<Document>();    dm.AddDocument(new Document("Title A", "Sample A"));    dm.AddDocument(new Document("Title B", "Sample B"));    dm.DisplayAllDocuments();    if (dm.IsDocumentAvailable)    {       Document d = dm.GetDocument();       Console.WriteLine(d.Content);    } } 

The DocumentManager now works with any class that implements the interface IDocument.

In the sample application, you’ve seen an interface constraint. Generics support several constraint types:

Open table as spreadsheet

Constraint

Description

where T : struct

With a struct constraint, type T must be a value type.

where T : class

The class constraint indicates that type T must be a reference type.

where T : IFoo

where T : IFoo specifies that type T is required to implement interface IFoo.

where T : Foo

where T : Foo specifies that type T is required to derive from base class Foo.

where T : new()

where T : new() is a constructor constraint and specifies that type T must have a default constructor.

where T : U

With constraints it is also possible to specify that type T derives from a generic type V. This constraint is known as naked type constraint.

Tip 

With CLR 2.0 only constructor constraints for the default constructor can be defined. It is not possible to define a constructor constraint for other constructors.

With a generic type, you can also combine multiple constraints. The constraint where T : IFoo, new() with the MyClass<T> declaration specifies that type T implements the interface IFoo and has a default constructor:

  public class MyClass<T>    where T : IFoo, new() {    //... 

Important 

One important restriction of the where clause with C# 2.0 is that it’s not possible to define operators that must be implemented by the generic type. Operators cannot be defined in interfaces. With the where clause, it is only possible to define base classes, interfaces, and the default constructor.

Inheritance

The LinkedList<T> class created earlier implements the interface IEnumerable<T>:

  public class LinkedList<T> : IEnumerable<T> {    //... 

A generic type can implement a generic interface. The same is possible by deriving from a class. A generic class can be derived from a generic base class:

  public class Base<T> { } public class Derived<T> : Base<T> { } 

The requirement is that the generic types of the interface must be repeated, or the type of the base class must be specified, as in this case:

  public class Base<T> { } public class Derived<T> : Base<string> { } 

This way, the derived class can be a generic or nongeneric class. For example, you can define an abstract generic base class that is implemented with a concrete type in the derived class. This allows you to do specialization for specific types:

  public abstract class Calc<T> {    public abstract T Add(T x, T y);    public abstract T Sub(T x, T y); } public class SimpleCalc : Calc<int> {    public override int Add(int x, int y)    {       return x + y;    }    public override int Sub(int x, int y)    {       return x - y;    }  } 

Static Members

Static members of generic classes require special attention. Static members of a generic class are only shared with one instantiation of the class. Let’s have a look at one example. The class StaticDemo<T> contains the static field x:

  public class StaticDemo<T> {    public static int x; } 

Because of using the class StaticDemo<T> both with a string type and an int type, two sets of static fields exist:

  StaticDemo<string>.x = 4; StaticDemo<int>.x = 5; Console.WriteLine(StaticDemo<string>.x);    // writes 4 




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