Generics is a new feature added to the Whidbey CLR and languages. We are still learning the best way to take advantage of the feature in reusable library APIs. The guidelines below represent out current thinking in this area.

1.0 Generics

A new feature in CLR that allows classes, structures, interfaces, and methods to be parameterized by the types of data they store and manipulate, through a set of features known collectively as generics.  CLR generics will be immediately familiar to users of generics in Eiffel or Ada, or to users of C++ templates, though they do not suffer many of the complications of the latter.

Without generics, programmers can store data of any type in variables of the base type object. To illustrate, let’s create a simple Stack type with two actions, “Push” and “Pop”.  The Stack class stores its data in an array of object, and the Push and Pop methods use the object type to accept and return data, respectively:

public class Stack {
   private object[] items = new object[100];

   public void Push(object data) {...}

   public object Pop() {...}
}

We can then push a value of any type, for example a Customer type, onto the stack. However, when we wanted to retrieve the value, we would need to explicitly cast the result of the Pop method, an object, into a Customer type, which is tedious to write and carries a performance penalty for run-time type checking:

Stack s = new Stack();
s.Push(new Customer());
Customer c = (Customer)s.Pop();

If we pass a value type, such as an int, to the Push method, it will automatically be boxed.  Similarly, if we want to retrieve an int from the stack, we would need to explicitly unbox the object type we obtain from the Pop method:

Stack s = new Stack();
s.Push(3);
int i = (int)s.Pop();

The boxing and unboxing operations can be particularly slow when done repeatedly.

Furthermore, in our current implementation, it is not possible to enforce the kind of data placed in the stack.  Indeed, we could create a stack and push a Customer type onto it.  Later, we could use the same stack and try to pop data off of it and cast it into a different type:

Stack s = new Stack();
s.Push(new Customer());
Employee e = (Employee)s.Pop();

While the code above is an improper use of the Stack class we want to implement and should be a compile-time error, it is actually legal code and the compiler will not have a problem with it.  At run-time, however, the application will fail because we have performed an invalid cast operation.

Generics provide a facility for creating high-performance data structures that are specialized by the compiler and/or execution engine based on the types that they use.  These so-called generic type declarations are created so that their internal algorithms remain the same, but so that the types of their external interface and internal data can vary based on user preference.

In order to minimize the learning curve for developers, generics are used in much the same way as C++ templates.  Programmers can create classes and structures just as they normally have, and by using the angle bracket notation (< and >) they can specify type parameters. When the generic class declaration is used, each type parameter must be replaced by a type argument that the user of the class supplies.

In the example below, we create a Stack generic class declaration where we specify a type parameter, called ItemType, declared in angle brackets after the declaration.  Rather than forcing conversions to and from object, instances of the generic Stack class will accept the type for which they are created and store data of that type without conversion. The type parameter ItemType acts as a placeholder until an actual type is specified at use. Note that ItemType is used as the element type for the internal items array, the type for the parameter to the Push method, and the return type for the Pop method:

public class Stack<T> {
   private T[] items;

   public void Push(T data) {...}

   public T Pop() {...}
}

When we use the generic class declaration Stack, as in the short example below, we can specify the actual type to be used by the generic class.  In this case, we instruct the Stack to use an int type by specifying it as a type argument using the angle notation after the name:

Stack<int> stack = new Stack<int>();
stack.Push(3);
int x = stack.Pop();

In so doing, we have created a new constructed type, Stack<int>, for which every ItemType inside the declaration of Stack is replaced with the supplied type argument int. Indeed, when we create our new instance of Stack<int>, the native storage of the items array is now an int[] rather than object[], providing substantial storage efficiency. Additionally, we have eliminated the boxing penalty associated with pushing an int onto the stack. Further, when we pop an item off the stack, we no longer need to explicitly cast it to the appropriate type because this particular kind of Stack class natively stores an int in its data structure.

If we wanted to store items other than an int into a Stack, we would have to create a different constructed type from Stack, specifying a new type argument.  Suppose we had a simple Customer type and we wanted to use a Stack to store it. To do so, we simply use the Customer class as the type argument to Stack and easily reuse our code:

Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
Customer c = stack.Pop();

Of course, once we’ve created a Stack with a Customer type as its type argument, we are now limited to storing only Customer objects (or objects of a class derived from Customer). Generics provide strong typing, meaning we can no longer improperly store an integer into the stack, like so:

Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
stack.Push(3);                 // compile-time error
Customer c = stack.Pop();      // no cast required

1.1 Generics Related Terminology

Term

Description

Generics

A new CLR feature that allows classes, structures, interfaces, and methods to be parameterized by the types of data they store and manipulate.

Generic Type Declaration

Declaration of a generic type. For example,

public class List<T> {

    public void Add(T item) { … }

}

Generic Method

For example,

public class Utils {

    public static void Swap<T>(ref T ref1, ref T ref2){ … }

}

Type Parameter

For example, in the following declaration, K and V are type parameters

public class Dictionary<K,V> {

    …

}

Type Argument

For example, in the following sample, string and int are both type arguments.

Dictionary<string,int> map = new Dictionary<string,int>();

Type Parameter Constraint, or Constraint

For example, in the following sample T is constrained to IComparable.

public class List<T> where T:IComparable {

    public void Add<T>(T item) { … }

}

Constructor Constraint

For example, in the following sample T has a constructor constraint.

public class List<T> where T:new() {

    public AddNew() {

        this.Add(new T())

    }

}

Constructed Type

Any type that has type arguments. For example, List<int>.

Open Constructed Type

A constructed type that involves one or more type parameters, e.g. List<T> or Dictionary<string,T> where T is a type parameter that is in scope. Intuitively, an open constructed type is "open" because one or more actual arguments for type parameters still need to be specified. It is not possible to have actual object instances of an open constructed type.

Closed Constructed Type

A constructed type that involves no type parameters, e.g. List<int> or Dictionary<string,int>. Object instances of generic types are always of closed constructed types because it is only possible to create object instances when all type arguments are known.

1.2 General Generics Usage Guidelines

Consider using Generics in public APIs. Generics can significantly improve cleanness of managed APIs. They allow for strong typing, which is great for usability and performance (by avoiding boxing of value types).

Tradeoff: APIs using some advanced features of Generics may be too difficult to use for some developers. The concept of Generics is not widely understood, in some cases the syntax may pose problems, and as any large new feature, Generics may pose a significant learning curve for some entry-level developers. Section 1.2.1 describes the details of usability issues related to Generics.

Do familiarize yourself with the performance impact of Generics before exposing or calling any Generic APIs. Generics can significantly impact the performance of APIs (positively or negatively). They are significantly different from C++ templates in regards to performance. Section 1.2.2 has details on the subject.

1.2.1 Generics and Usability

One of the main reasons to use Generics in reusable APIs is to improve usability by making the APIs more strongly typed. Hopefully it is obvious that strongly typed APIs are much easier to use. The benefit is especially visible when using source code editors supporting statement completion.

Unfortunately, Generics can also have a negative impact on usability. As any large new feature, Generics may pose a significant learning curve for some entry-level developers. The syntax may pose problems, especially in some more advanced cases. And finally, advanced usage of generics may be conceptually difficult for some developers. This section will describe some of the usability issues related to Generics.

Note: This chapter is based on preliminary usability studies conducted with some limited set of Generic APIs. More research needs to be done to asses the full impact of Generics on usability. We continue working on getting such studies conducted.

Generic types may be difficult to instantiate because:

Users may not be familiar or proficient with the syntax of Generics. This is particularly true when users have to instantiate types with a type argument that is in itself a generic type.

List<Nullable<int>> list = new List<Nullable<int>>();

Users may have problems understanding what the Generic parameter represents. This is usually not a problem for simple collections, where it’s rather obvious that the parameter represents the type of the items stored in the collection. It is more problematic in cases where the type parameter relationship to the Generic type is not obvious.

DataSession<Presence> session = new DataSession<Presence>(…);

Users may have problems finding types that satisfy the constraints.

Generic methods may be difficult to use because:

Users may have problems understanding what the Generic parameter represents.

Users may not be familiar or proficient with the syntax calling Generic methods. They often confuse the type parameters with the regular method parameters.

Usability Guidelines

Do freely use Generics when returning instances of the Generic types from object models. In such cases, the user does not have to instantiate the Generic type and for the most part the user does not have to be even aware that the returned object is an instance of a Generic type.

public class Directory {

   public Directory(string root) { … }

   public IEnumerable<FileInfo> Files { get { … } }

}

Directory croot = new Directory(@”c:\”);

foreach(FileInfo file in croot){

   Console.WriteLine(file.Name);

}

Do use Generic collections. The concept of a Generic collection is relatively easy to understand. List<String> is only syntactically, but not conceptually, different from StringCollection. See section for details.

Avoid Generic types with more than 2 type parameters in high-level APIs. Users have difficult understanding what type parameters represent in types with long type parameter lists.

Avoid high-level APIs that require users to instantiate a generic type with another generic type as the type argument. The syntax gets too complex for some users.

Foo<Bar<int>> foo = new Foo<Bar<int>>();

     Requiring that users use triple, or higher, nesting is unacceptable in high-level APIs.

Foo<Bar<Nullable<int>>> foo = new Foo<Bar<Nullable<int>>>();

Avoid Generic methods that don’t support type parameter inference in high-level APIs. Such methods are often very difficult to call because of the difficulty in understanding the purpose of the type parameter and because of the somewhat difficult syntax.

Methods with a formal parameter typed as the generic method type parameter support inference. Methods with no formal parameter typed as the generic method type parameter don’t support inference.

public static class RandomHelper {
   public static T ChooseOne<T>(params T[] set) {
       return set[rand.Next(set.Length)];
   }

 

static Random rand = new Random();
}

 

public class Assembly {

   public T[] GetAttributes<T>() where T:Attribute { … }

}

The RandomHelper.ChooseOne method can be called without explicitly specifying a type argument. The type argument is inferred from the arguments passed to the method. The Assembly.GetAttributes does not support inference and the type parameter needs to be specified explicitly.

int i = Util.ChooseOne(5, 213);      // Calls Choose<int>

string s = Util.ChooseOne("foo", "bar");     // Calls Choose<string>

AssemblyVersionAttribute version = assembly.GetAttribute<AssemblyVersionAttribute>();

Do freely use Generic methods that support type parameter inference. Such methods hit the sweet spot of Generics. They support a very convenient syntax.

public static class Reference {

   public static void Swap<T>(ref T ref1, ref T ref2){

   T temp = ref1;

   ref1 = ref2;

   ref2 = temp;

}

}

 

// Usage

string s1 = “World”;

string s2 = “Hello”;

// notice that the type argument does not need to be specified

Reference.Swap(ref s1,ref s2);

Do not have static members on Generic types in high-level APIs.

Some users may not understand the difference between a generic method and a static method on a generic type. They both require the user to pass type arguments, but have slightly different syntax.

Foo<int>.GetValues(); // static member on a Generic type

Foo.GetValues<int>(); // Generic method

1.2.2 Generics and Performance

Do consider the performance ramifications of generics. Specific recommendations arise from these considerations and are described in guidelines that follow.

Execution Time Considerations

Generic collections over value types (e.g. List<int>) tend to be faster than equivalent collections of Object (e.g. ArrayList) because they avoid boxing items.

Generic collections over all types also tend to be faster because they do not incur a checked cast to obtain items from the collection.

The static fields of a generic type are replicated, unshared, for each constructed type. The class constructor of a generic type is called for each constructed type. For example,

public class Counted<T> {

   public static int count;

   public T t;

   static Counted() {

      count = 0;

   }

   Counted(T t) {

      this.t = t;

      ++count;

   }

}

Each constructed type Counted<int>, Counted<string>, etc. has its own copy of the static field, and the static class constructor is called once for each constructed type. These static member costs can quietly add up. Also, accesses to static fields of generic types may be slower than accesses to static fields of ordinary types.

Generic methods, being generic, do not enjoy certain JIT compiler optimizations, but this is of little concern for all but the most performance critical code. For example, the optimization that a cast from a derived type to a base type need not be checked is not applied when one of the types is a generic parameter type.

Code Size Considerations

The CLR shares IL, metadata, and some JIT’d/NGEN’d native code across types/methods constructed from generic types/methods. Thus the space cost of each constructed type is modest, less than that of an empty conventional non-generic type. But see also ‘current limitations’ below.

When a generic type references other generic types, then each of its constructed types constructs its transitively referenced generic types. For example, List<T> references IEnumerable<T>, so use of List<string> also incurs the modest cost of constructing type IEnumerable<string>.

In summary, from the performance perspective, generics are a sometimes-efficient facility that should be applied with great care and in moderation.

When you employ a new constructed type formed from a pre-existing generic type, with a reference type parameter, the performance costs are modest but not zero.

The stakes are much higher when you introduce new generic types and methods for use internal or external to your assembly. If used with value type parameters, each method you define can be quietly replicated dozens of times (when used across dozens of constructed types).

Do use the pre-defined System.Collections.Generic types over reference types. Since the cost of each constructed type AGenericCollection<ReferenceType> is modest, it is appropriate to employ such types in preference to defining new strongly-typed-wrapper collection subtypes. Any of the following are appropriate:

public class Sample {

    public void Method(IEnumerable<MyRefType> param);

    public void Method(ICollection<MyRefType> param);

    public void Method(IList<MyRefType> param);

    public void Method(CustomCollection param);

    public IEnumerable<MyRefType> Property1 { get; }

    public ICollection<MyRefType> Property2 { get; }

    public IList<MyRefType> Property3 { get; }

    public Collection<MyRefType> Property4 { get; }

    public KeyedCollection<MyRefType> Property5 { get; }

    public CustomCollection Property6 { get; }

}

 

public class CustomCollection : Collection<MyRefType> {}

public class CustomCollection2 : KeyedCollection<MyRefType> {}

public class CustomCollection3 : IList<MyRefType> {}

public class CustomCollection4 : ICollection<MyRefType> {}

Do use the pre-defined System.Collections.Generic types over value types in preference to defining a new custom collection type. As was the case with C++ templates, there is currently no sharing of the compiled methods of such constructed types – e.g. no sharing of the native code transitively compiled for the methods of List<MyStruct> and List<YourStruct>. Only construct these types when you are certain the savings in dynamic heap allocations of avoiding boxing will pay for the replicated code space costs.

Do use Nullable<T> and EventHandler<T> even over value types. We will work to make these two important generic types as efficient as possible.

Do not introduce new generic types and methods without fully understanding, measuring, and documenting the performance ramifications of their expected use.

1.2.3 Common Patterns

The following section describes some common patterns where Generics can help in providing cleaner APIs.

Do use generics to provide strongly typed collection APIs. See section 1.4.2 for details.

Consider using generics whenever an API returns Object (or any other type that is not as strong typed as you would wish).

Without Generics

public class WeakReference {

    public WeakReference(object target){ … }

    public object Target { get { … } }

}

string s = “Foo”;

WeakReference r = new WeakReference(s);

s = (string)r.Target;

With Generics

public class WeakReference<T> {

   public WeakReference(T target) { … }

   public T Target { get { … } }

}

string s = “Foo”;

WeakReference<string> r = new WeakReference<string>(s);

S = r.Target;

Consider using generics whenever an API causes boxing.

Without Generics

public class ArrayList {

   public void Add(object value) { … } // this will box any value type

}

ArrayList list = new ArrayList();

List.Add(1); // Int32 is a value type and it will get boxed here.

With Generics

public List<T> {

   public void Add(T value) { … }

}

List<int> list = new List<int>();

List.Add(1); // this one does not box

Consider using generics instead of manual code expansion of patterns that vary only on type. For example, strongly typed collections only differ on the type of the items being stored in the collections. Generics can be used to provide a single type supporting such variations instead of many, often inconsistent variations like StringCollection, TokenCollection, AttributeCollection, etc.

public class List<T> : IList<T> {

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

   public void Add(T);

   public IEnumerator<T> GetEnumerator();

   …

}

Similarly, today EventHandler needs to be inherited just to change the type of EventArgs. With the use of generics, we can define a generic event handler:

public delegate void EventHandler<T>(object sender, T args) where T:EventArgs;

Consider using generics whenever an API takes a parameter of type System.Type. For example, currently one of the overloads of Attribute.GetCustomAttribute is defined as follows:

class ObjectSpace {

    object GetObject(Type type, String queryString, String span);

}

It could be implemented as a generic method, which would provide cleaner syntax and compile time type safety.

class ObjectSpace {

    T GetObject<T>(String queryString, String span);

}

This needs to be done carefully. There are a large number of methods that take Type parameters and should not be generic. For example, it does not make sense for Activator.CreateInstance(Type) to be implemented as a generic method. The whole point of CreateInstance is that the type parameter is late bound and not hard-coded at the call site, in which case the operator new is a better way to create the instance.

Consider using generics whenever a type variant API requires ref parameters.

References passed to ref parameters must be the exact type of the parameter (a reference to string is not a subclass of a reference to an object). For example, it’s illegal to pass a reference to a string reference to a method that takes a reference to an object reference. This means that, for example, a general purpose Swap routine based on object references is completely impractical.

Without Generics

public sealed class Reference {

   public static void Swap(ref object o1, ref object o2){

       object temp = o1;

       o1 = o2;

       o2 = temp;

   }

}

 

string s1 = "Foo";

string s2 = "Bar";

 

//Note this will not compile: Reference.Swap (s1, s2);

object o1 = s1;

object o2 = s2;

Reference.Swap(ref o1, ref o2);

s1 = (string)o1;

s2 = (string)o2;

Generics solve the problem, and quite elegantly so.

With Generics

public sealed class Reference {

    public static void Swap<T>(ref T ref1, ref T ref2){

       T temp = ref1;

       ref1 = ref2;

       ref2 = temp;

    }

}

 

// Using Generics

string s1 = "Foo";

string s2 = "Bar";

Reference.Swap(ref s1, ref s2);

Consider using Generics whenever you need a method to take a parameter that implements two or more unrelated interfaces. For example, the following method takes a parameter that is a subclass of Stream and implements IEnumerable<Byte>.

void Read<T>(T enumerableStream) where T:Stream,IEnumerable<Byte> {

   if(enumerableStream.CanRead){

       foreach(Byte b in snumerableStream){

           Console.Write(b);

       }

   }

}

1.3 Naming Guidelines

Do name generic type parameters with single letters. For example, prefer Dictionary<K,V> to Dictionary<Key,Value>.

Note: One of the main reasons for this guideline is that we noticed people need to be able to distinguish between type parameters and type names. For example, it’s not clear whether the return type of the following method is a generic parameter or a real type: public Value GetValue(); Using single letters for type parameters makes this clear.

Consider using T as the type parameter name for types with a single type parameter.

Consider including indication of the constraint in the name of Generic types and method parameters.

public class DisposableReference<T> where T:IDisposable {

}

void Read<T>(T enumerableStream) where T:Stream,IEnumerable<Byte> {

}

1.4 Usage Guidelines

1.4.1 General Usage

Do not use unnecessary constraints. For example, if a Generic method calls members exposed on System.IO.Stream, type parameter should be constrained to System.IO.Stream, not to System.IO.FileStream.

void PrintLengths<T>(IEnumerable<T> streamCollection) where T:Stream {

   foreach(Stream stream in streamCollection){

       Console.WriteLine(stream.Length);

   }

}

Consider including the constraint type in the parameter related to the constraint. For example, if a method has a parameter ICollection<T> and T is constrained to System.IO.Stream, the parameter should be called streamCollection.

void PrintLengths<T>(IEnumerable<T> streamCollection) where T:Stream;

Avoid having “hidden” constraints. For example, you should not dynamically check the type argument and throw if it does not implement some interface.

public static class Interlock{

   public static T Exchange<T>(ref T location, T value){ // no constraint

       // do not do this! This is a hidden constraint

       if(T.default.GetType().IsValueType){

           throw new InvaludOperationException(…);

       }

   }

}

Note: The only scenario where we allow hidden constraints is when you would like to constrain the type parameter to either of several types. In such case, you should constrain the parameter to the most derived type common to all of the alternatives or do not use a constraint at all if System.Object is the only common base. For example, a generic parameter that can either accept IComparable or IComparable<T> should not have a constraint. If the hidden constraint is not satisfied, an InvalidOperationException should be thrown.

Do not place generic types in special generic-only assemblies just because the types are generic. It is perfectly fine to place generic types in assemblies with non-generic types. In other words, whether a type is generic or not should not affect which assembly it resides in.

Do not place generic types in special generic-only namespaces just because the types are generic. It is perfectly fine to have generic types in namespaces with non-generic types.

Annotation (Krzysztof Cwalina): We placed the generic collections in a separate namespace from the non-generic ones because we believed that users will rarely want to, or need to, import both of the namespaces at the same time. We basically see the namespaces (most types in the namespaces) as alternatives, not as parts of one feature area. This may not be true for some scenarios where the non-generic collection interfaces implemented on the generic collections are used, but these are considered relatively advanced scenarios.

1.4.2 Generic Collections

Do prefer generic collection over the non-generic collections. You can optionally provide overloads accepting the non-generic interfaces.

static class Enumerable {

    static void PrintToConsole<T>(IEnumerable<T> collection);

    static void PrintToConsole(IEnumerable collection); // optional

}

public class Directory {

   public Directory(string root);      

   public IEnumerable<FileInfo> Files { get; }

}

Do type method parameters as the least specialized interface that will to the job. For example, if a method just enumerated entries in a collection, the method should take IEnumerable<T>, not IList<T> or List<T>.

static void PrintToConsole<T>(IEnumerable<T> collection){

   foreach(T item in collection){

       Console.WriteLine(item);

   }

}

Consider returning a subclass of a standard collection from high level APIs, even if you don’t presently plan to provide any customization. If your API returns one of the interfaces (IEnumerable<T>, IList<T>, etc.) or one of the standard collections (Collection<T>, ReadOnlyCollection<T>, etc.) the name of the type will not be as descriptive as possible and also you will not be able to add members to the APIs in the future.

public class FileInfoCollection : ReadOnlyCollection<FileInfo> {}

public class Directory {

   public Directory(string root);

   public FileInfoCollection GetFiles();

}

Do use either a non-indexed collection (IEnumerable<T>, ICollection<T>) or a method returning a snapshot when providing access to collections that are potentially unstable (i.e. can change without the client modifying the collection). In general all collections representing shared resource (a collection of files in a directory) are unstable.

public class Directory {

   public Directory(string root);      

   public IEnumerable<FileInfo> Files { get; }

   public FileInfoCollection GetFiles();

}

Do not return “snapshot” collections from properties. Property getters should return “live” collections.

public class Directory {

   public Directory(string root);

   public IEnumerable<FileInfo> Files { get; } // returns live collection 

}

Do not return “live” collections from methods. Methods should always return Collections returned from methods should be snapshots.

public class Directory {

   public Directory(string root);

   public FileInfoCollection GetFiles(); // returns snapshot collection

}

Do return Collection<T> from object models to provide standard plain vanilla collection API.

public Collection<Session> Sessions { get; }

Do return a subclass of Collection<T> from object models to provide high-level collection API.

public class ListItemCollection : Collection<ListItem> {}

public class ListBox {

   public ListItemCollection Items { get; }

}

Do not return List<T> from object models. Use Collection<T> instead. List<T> is meant to be used for implementation, not in object model APIs. List<T> is optimized for performance at the cost of long term versioning. For example, if you return List<T> to the client code, you will not ever be able to receive notifications when client code modifies the collection.

Do return ReadOnlyCollection<T> from object models to provide plain vanilla read-only collection API.

public ReadOnlyCollection<Session> Sessions { get; }

Do return a subclass of ReadOnlyCollection<T> from object models to provide high level read-only collection APIs.

public class XmlAttributeCollection : ReadOnlyCollection<XmlAttribute> {}

public class XmlNode {

   public XmlAttributeCollection Attributes { get; }

}

Note: When inheriting from ReadOnlyCollection, name it XxxCollection. If you have both read-only and read/write versions of the same collection, name them ReadOnlyXxxCollection and XxxCollection, not XxxCollection and XxxFileInfoCollection.

Do use a subclass of KeyedCollection<K,T> for collections of items that have natural unique names. For example, a collection of files in a directory could be represented as a subclass of KeyedCollection<string,FileInfo> where string is the filename. Users of the collection could then index the collection using filenames.

public class FileInfoCollection : KeyedCollection<string,FileInfo> {

   …

}

public class Directory {

   public Directory(string root);

   public FileInfoCollection GetFiles();

}

Do use ICollection<T> for unordered collections.

Do implement IEnumerable<T> on strongly typed non-generic collection. Consider implementing ICollection<T> or even IList<T> where it makes sense.

1.4.3 Nullable<T>

Do use Nullable<T> to represent values that are optional. For example, database wrappers returning values from columns that allow null values or XML wrappers returning values of optional attributes.

public class Customer{

   internal Customer(ISqlCommand command) { … }

   internal Customer(XmlReader xml) { … }

 

   public string Name { get; set; } // name is not optional

   public Nullable<int> Age { get; set; } // age is optional

   public Nullable<string> EMail { get; set; } // email is optional

}

// print customers with specified age

foreach(Customer customer in customerList){

   if(customer.Age.HasValue){

       Console.Writeline(“{0} : {0}”,customer.Name,customer.Age);

   }

}

Annotation [AndersH]: When you design APIs using Nullable<T>, you have to be very sensitive to the fact that a Nullable<T> is a lot less convenient to use than a T. Nullable<T> with value types makes sense and solves a real world problem--therefore, users will be amenable to the inconveniences of using it. However, Nullable<T> with reference types makes little sense and solves no real world problem. In fact, it adds nothing but confusion because, in terms capabilities, there is no difference between a string and a Nullable<string>. With respect to having a Value Type constraint on Nullable<T>, we haven't done it because it would severely limit Nullable<T>'s use in generic scenarios. For example, imagine a Find method that returns a T or a null value when an item isn't found. In the generic world, a possible solution is to return Nullable<T>, but only if Nullable<T> works for all types.

 

If you are shipping pre-Longhorn you will need to provide a CLS compliant alternative. The CLS compliant alternative to the APIs is to use boxed value types. For example:

public class Customer{

      public string Name { get; set; }

      public object Age { get; set; } // this is actually int or null (if Age does not have a value)

}

 

Second alternative is to use a pair of properties. It has the disadvantage of cluttering the API, but the benefit of strong typing and avoiding boxing:

public class Customer{

     public string Name { get; set; }

     public int Age { get; set; } // this is actually int or null (if Age does not have a value)

     public bool HasAge { get; set; }

}

Avoid using types from System.Data.SqlTypes in high level APIs. You should use Nullable<T> to represent optional or “nullable” values of Value Types.

1.4.4 EventHandler<T>

Do use System.EventHandler<T> instead of System.EventHandler. Do not manually create new delegates to be used as event handlers.

internal class NotifyingContactCollection : Collection<Contact> {

   public event EventHandler<ContactAddedEventArgs> ContactAdded { … }

   protected override void Add(Contact contact){

       base.Add(contact);

       ContactAdded(this,new ContactAddedEventArgs(this.Count-1));

   }

internal class ContactAddedEventArgs : EventArgs {

   public ContactAddedEventArgs(int index){ .. }

   public int Index { get ; }

}

1.4.5 Comparer<T>

Do use System.Comparer<T> instead of other means of comparing types, like System.Comparer, Object.Equals, etc.