HowTo – Generics in C#

Source Code

All source code can be found on GitHub here.

My cheat sheet in generics can be found here while some code examples in generics can be found in my post here.

This is part of my HowTo in .NET seriies. An overview can be seen here,

 

Why Generics?

To understand generics we need to understand why it was created. To understand why it was created we need to understand the problems it solved.

What is the issue with the following code?

var arrayList = new ArrayList();
arrayList.Add(1);
int x = (int)arrayList[0];

Traditional collections required casting to and from object. Value types required boxing and unboxing

What is the issue with the following code?

var arrayList = new ArrayList();
arrayList.Add(1);
Var x = (string)arrayList[0];

Traditional collections are not type safe.

Consider This


using System;
using System.Collections;

public class Int16Collection : CollectionBase  {

   public Int16 this[ int index ]  {
      get  {
         return( (Int16) List[index] );
      }
      set  {
         List[index] = value;
      }
   }

   public int Add( Int16 value )  {
      return( List.Add( value ) );
   }

   public int IndexOf( Int16 value )  {
      return( List.IndexOf( value ) );
   }

   public void Insert( int index, Int16 value )  {
      List.Insert( index, value );
   }

   public void Remove( Int16 value )  {
      List.Remove( value );
   }

   public bool Contains( Int16 value )  {
      // If value is not of type Int16, this will return false.
      return( List.Contains( value ) );
   }

   protected override void OnInsert( int index, Object value )  {
      // Insert additional code to be run only when inserting values.
   }

   protected override void OnRemove( int index, Object value )  {
      // Insert additional code to be run only when removing values.
   }

   protected override void OnSet( int index, Object oldValue, Object newValue )  {
      // Insert additional code to be run only when setting values.
   }

   protected override void OnValidate( Object value )  {
      if ( value.GetType() != typeof(System.Int16) )
         throw new ArgumentException( "value must be of type Int16.", "value" );
   }
}

Implementing type safe collections is tedious and results in duplicate code

Consider This

using System;

public static class ParseFromString
{
    public static Int16 ParseInt16(string aString)
    {
        Int16 outPut;
        return Int16.TryParse(aString, out outPut) ? outPut : default(Int16);
    }

    public static Int32 ParseInt32(string aString)
    {
        Int16 outPut;
        return Int32.TryParse(aString, out outPut) ? outPut : default(Int32);
    }

    public static Int64 ParseInt64(string aString)
    {
        Int64 outPut;
        return Int64.TryParse(aString, out outPut) ? outPut : default(Int64);
    }
}

Multiple overloaded functions which vary only by the parameter type and contains duplicate code

Issues Pre Generics

Poor Performance

  • Poor Performance
    • Collections always worked upon objects Casting is relatively slow
    • Value types have to be boxed and unboxed during casting
    • Boxing is packaging a value type in an instance of the Object reference type.
      • Requires creation and allocation of memory allocation upon the heap when boxing
      • Reference will need to be garbage collected later
      • Requires creation and allocation of memory allocation upon the stack when unboxing
      • Lose the performance benefits of working on the stack
  • Not Type Safe
    • Any reference and value type can be cast to and from Object
    • Errors can be thrown casting to the wrong type
    • Extra checks can be provided but relatively slow
  • Type Safe collections
    • CollectionBase could be sub-classed to provide a type safe collection
    • One required for each type
    • Duplicated existed
    • Sill required boxing and unboxing
    • Duplicate Code
    • Multiple overloaded function which only varied by their type

What is generics

  • Generics provide a template or place holder for a type
  • The type is determined a compile time
  • The type can not be changed
  • No casting
  • No boxing / unboxing
  • Value items stay on the stack

Generics As Parameters

Var list = new List< int >();
var dict = new Dictionary<int,String>();

Collection Initialization

You can apply collection initialization syntax only to classes that support an Add() method and implement the ICollection/ICollection interfaces.

Var list = new List(){1,2,3}; // Collection Initialization

Type Inference

Inference of Type Parameters can be made by the compiler. Cleaner code to specify them though.

var strings = new List {"first", "second"};
strings.Where< string >(x => , x.StartsWith("s"));
strings.Where(x => , x.StartsWith("s"));

A Sample Generic Method

public static T Parse (this String toParse, T defaultValue) where T : struct
{
    T? value = TryParse (toParse);
    return value.HasValue ? value.Value : defaultValue;
}

Where a type would normally be replace with a template type
Template type can be named anything though normally:

  • T,U,V,W
  • TOutput, TKey, TValue

Constraints

where T: struct // Value Type; System.ValeValue is found as an ancestor
where T: class  // Reference type; System.ValeValue is not found as an ancestor (any class, interface, delegate or array type)
where T: new()  // Provides a parameterless constructor.
where T:        // Has an ancestor
where T:        // Implements an interface
where T: U      // One provided type is an ancestor of another.

Examples

class Test< T, K > where K : struct where T : Base, new() {}
class List{ void Add(List items) where K : T {}
class SampleClass< T, K, V > where T : V {}
void SomeFunction(T aValue) where T : V {}

Operators and Equality

Operators are not supported

T + T

Equality operators are also not suported

!= // and
== // can not be used

Can compare to null though value types will always return null

Default Value

return default(T); // returns the default value of the type; 0 for int "" for String

Casting

Consider the following

class MyClass where T : BaseClass, ISomeInterface {}

Implicit Casting

ISomeInterface obj1 = t;
BaseClass obj2 = t;
object obj3 = t;

Explicit Casting

ISomeInterface obj1 = (ISomeInterface)t; //Compiles
SomeClass obj2 = (SomeClass)t; //Does not compile
  • Can cast to and from System.Object and explicitly convert to any interfaces.
  • Implicit cast can be to object or to constraint-specified types only.
  • Implicit casting is type safe as incompatibility is discovered at compile-time.
  • Explicit cast can be to interface only

Is and As

It is better to use the is and as operator than explicit casting

IS

  • expression is not null.
  • expression can be cast to type
if(t is int)
if(t is LinkedList)

AS

Will cast where possible or return null/default(T)

like a cast but returns null on failure
t as IDisposable
expression is type ? (type)expression : (type)null // equivalent to as
using(t as IDisposable) { }

Converting Between Types

Parse and Try Parse

Integer.Parse("1")
Integer.TryParse( "1", out aValue)

IsAssignableFrom

bool IsInterface = typeof(ISomeInterface).IsAssignableFrom(typeof(T));

Change Type

Returns an object of the specified type and whose value is equivalent to the specified object.

int i = (int)Convert.ChangeType(-2.345, typeof(int));
DateTime dt = (DateTime)Convert.ChangeType("12/12/98", typeof(DateTime));

TypeDescriptor Converter

var foo = TypeDescriptor.GetConverter(typeof(T));

//foo.<a href="http://msdn.microsoft.com/en-us/library/a1155ebw.aspx">CanConvertTo(Type);</a>
//foo.<a href="http://msdn.microsoft.com/en-us/library/a1155ebw.aspx">CanConvertFrom(Type);
</a>
return (T)(foo.ConvertFromInvariantString(mystring));

Type Analyses

TypeCode Enum

TypeCode typeCode = Type.GetTypeCode( testObject.GetType() );
TypeCode.Boolean;
TypeCode.Decimal;

IsType

typeof(int).IsPrimitive
typeof(int).IsDecimal
typeof(int).IsArray
typeof(int).IsInterface

Attributes

These can not be used however you can check owning functions

myobj.GetType().IsSerializable

When Should I use generics

When you have multiple method overloads which have the same functionality for multiple types

When Should I not use generics

If you find yourself restricting access to an Interface or a base class then you should ask yourself if generics is really required here

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s