domain.dot.net team

06.27.08

Master Generic Type Constrains : A critical success factor in Linq Development

Filed under: Generics — Damon Wilder Carr @ 12:07 pm
Tags: , , , , , , ,

 

More accurately called ..‘Variance and Generalized Constraints for C# Generics’) (1)

An item we often see teams ‘leaving money on the table’ with is a failure to leverage (or leverage fully) generic type constraints. Now with Linq you cannot get away with it anymore as for now, although far more powerful, there are still long lines of complex generic delegate types to navigate.

This post based on the docs from MSDN, however we’ve added some significant teeth we believe (with more to come) We at least made this potentially more ‘real world’ in complexity.

When you define a generic class, you can apply restrictions to the kinds of types that client code can use for type arguments when it instantiates your class. If client code tries to instantiate your class by using a type that is not allowed by a constraint, the result is a compile-time error. These restrictions are called constraints. Constraints are specified by using the where contextual keyword. (NOTE: the constraint is LITERAL unless specified otherwise)

where T: struct    (literal)

The type argument must be a value type. Any value type except Nullable can be specified.

See Using Nullable Types (C# Programming Guide) for more information.

where T : class   (literal)

The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.

where T : new()   (literal)

The type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last.

where T : SomeClassName   (non-literal)

Example:

public class StreamList<T> : List<T> where T : Stream

Here we are creating a custom collection from List<T> and also ensuring (see above) that values are reference types.

where T : someinterface    (non-literal)

The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic.

where T : U       (non-literal)

The type argument supplied for T must be or derive from the argument supplied for U. This is called a naked type constraint.

domain.dot.net addition

Here is an example of a pattern we use that combines the concept of generic constraints to make Extension methods far more effective.

Assume you want to allow any Enum to support seamless conversion to a Linq sequence an IEnumerable<TType> where TType : Enum

Wait! You cannot do that! Ugh…Enum is one of the invalid constraint types (as is delegate, Array, etc.)

Here is how we ‘solve’ this:

1) Assert as much as you can about a type

2) Do run-time checks if needed

Here is the header:

public static IEnumerable<TEnumType> EnumToLinq<TEnumType>
                this TEnumType target)

                     where TEnumType : struct, IComparable, IFormattable, IConvertible

If that immediately makes you feel sick, well this is what refactored code looks like where you try to not force an entire team to work daily here, but it’s unavoidable. After having our heads in this for a while you absolutely need to master this to be a senior Linq developer (and looking at others code is always fantastic for us as we find things we learn we were doing wrong).

And here is the full extension method (go ahead and use it for whatever you want if you find value):

public static IEnumerable<TEnumType> EnumToLinq<TEnumType>(

                 this TEnumType target)

                            where TEnumType : struct, IComparable,

                                  IFormattable, IConvertible {

                                  var _type = target.GetType();

                                   iif (!_type.BaseType.Equals(typeof (Enum)))

                                        throw new TypeInitializationException(

                                        _type.FullName, new InvalidOperationException(

                                           _type.Name + ” is not an Enum”

))

                                                                              ;

                  return Enum.GetValues(_type).Cast<TEnumType>(); }

Bottom line is we get a nice sequence we can use any Linq to Object expression on.

Here is an example of the above in use:

/// <summary>

/// Using a simple bitwise this for now returns

/// a predicate for later execution and resolution

/// of a FilleystemInfo. This is used now in Linq

/// queries to get back various types of things

/// on the hard disk.

/// </summary>

/// <value>To predicate.</value>

///

Documentation Created 6/27/2008

private static Predicate<FileSystemInfo> ToPredicate {

get {

                          // NOTE: This sets the 
                     

    // class value (we only need 1 static)

                          // as well as uses a simple bitwise 
                          // & to check if the Directory

                          // attribute is set

                          return _persistendPred =

                                  fileInfo => ((fileInfo.Attributes
                                   
   FileAttributes.Directory) ==

                                               FileAttributes.Directory); }

                               }

Back to MSDN…..

Why Use Constraints

If you want to examine an item in a generic list to determine whether it is valid or to compare it to some other item, the compiler must have some guarantee that the operator or method it has to call will be supported by any type argument that might be specified by client code. This guarantee is obtained by applying one or more constraints to your generic class definition. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. Constraints are applied by using the contextual keyword where. The following code example demonstrates the functionality we can add to the GenericList<T> class (in Introduction to Generics (C# Programming Guide)) by applying a base class constraint.

 

public class Employee { public Employee(string s, int i) { Name = s; ID = i; } public string Name { get; set; } public int ID { get; set; } }

    

 

public class GenericList<T> where T : Employee { private Node head; public GenericList() { head = null; } public void AddHead(T t) { var n = new Node(t) { Next = head }; head = n; } private class Node { public Node(T t) { Next = null; Data = t; } public Node Next { get; set; } public T Data { get; set; } }

blic IEnumerator<T> GetEnumerator() public T FindFirstOccurrence(string s) { var current = head; T t = null; while (current != null) if (current.Data.Name == s) { t = current.Data; break; } else current = current.Next; return t; } }

The constraint enables the generic class to use the Employee.Name property

because all items of type T are guaranteed to be either an Employee object or an object that inherits from Employee.

Multiple constraints can be applied to the same type parameter, and the constraints themselves can be generic types, as follows:

 

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new() { // … }

By constraining the type parameter, you increase the number of allowable operations and

method calls to those supported by the constraining type and all types in its inheritance hierarchy. Therefore, when you design generic classes or methods, if you will be performing any operation on the generic members beyond simple assignment or calling any methods not supported by System.Object, you will have to apply constraints to the type parameter.

When applying the where T : class constraint, avoid the == and != operators on the type parameter because these operators will test for reference identity only, not for value equality. This is the case even if these operators are overloaded in a type that is used as an argument. The following code illustrates this point; the output is false even though the String class overloads the == operator.

public static void OpTest<T>(T s, T t) where T : class {

       System.Console.WriteLine(s == t);

}

 static void Main() {

           string s1 = “foo”;

            var sb = new System.Text.StringBuilder(“foo”);

            string s2 = sb.ToString();

            OpTest<string>(s1, s2);

}

The reason for this behavior is that, at compile time, the compiler only knows that T is a reference type, and therefore must use the default operators that are valid for all reference types. If you must test for value equality, the recommended way is to also apply the where T : IComparable<T> constraint and implement that interface in any class that will be used to construct the generic class.

Unbounded Type Parameters

Type parameters that have no constraints, such as T in public class SampleClass<T>{}, are called unbounded type parameters. Unbounded type parameters have the following rules:

  • The != and == operators cannot be used because there is no guarantee that the concrete type argument will support these operators.
  • They can be converted to and from System.Object or explicitly converted to any interface type.
  • You can compare to null. If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type.

Naked Type Constraints

When a generic type parameter is used as a constraint, it is called a naked type constraint. Naked type constraints are useful when a member function with its own type parameter has to constrain that parameter to the type parameter of the containing type, as shown in the following example:

class List<T> { void Add<U>(List<U> items) where U : T {/*…*/} }

In the previous example, T is a naked type constraint in the context of the Add method, and an unbounded type parameter in the context of the List class.

Naked type constraints can also be used in generic class definitions. Note that the naked type constraint must also have been declared within the angle brackets together with any other type parameters:

 

//naked type constraint

public class SampleClass<T, U, V> where T : V { }

The usefulness of naked type constraints with generic classes is very limited because the

compiler can assume nothing about a naked type constraint except that it derives from System.Object. Use naked type constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

Burak Emir1, Andrew Kennedy2, Claudio Russo2, and Dachuan Yu3

1 EPFL, Lausanne, Switzerland

2 Microsoft Research, Cambridge, U.K.

3 DoCoMo Communications Laboratories USA, San Jose, California

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.