Category Archives: Generics
Generic types (part 2, basics, advanced)
We have looked into generic classes in part 1 https://csharphardcoreprogramming.wordpress.com/2014/01/01/generic-types-part-1-basics-advanced/.
On the following day I used a generic method when explaining reflection https://csharphardcoreprogramming.wordpress.com/2014/01/02/reflection-basics-advanced/.
Let’s look at generic delegates more closely. They look neat and make code, as the name says, more generic.
delegate T Calc<T>(T a, T b); private static int Add(int a, int b) { return a + b; } private static double Multiply(double a, double b) { return a * b; } public static void Generics8() { Calc<double> lProduct = Multiply; Calc<int> lSum = Add; Console.WriteLine("Product:" + lProduct(5.0, 2.0)); Console.WriteLine("Sum:" + lSum(5, 2)); Console.ReadKey(); } //
We can also use constraints on delegates:
delegate T Calc<T>(T a, T b) where T : struct;
Generally we know the pattern now. And without much explanation a quick summary should cover all generic types:
// generic delegate delegate T myDelegate<T>(T t) where T : struct; // generic method void myMethod<T, U>(T a, T b, U c) {} // generic class class myClass<T> where T : System.IComparable<T>, IEnumerable<T> {} // generic interface interface IMyInterFace<out TResult, in TInput> { TResult DoSometing(TInput Args); }
Interfaces can make use of the in and out keywords.
The out keyword declares a generic type parameter covariant. The in keyword makes it contravariant.
Covariance and contravariance were introduced when I described delegates https://csharphardcoreprogramming.wordpress.com/2013/12/16/delegates-basics/ .
The out keyword can only be used as an input parameter if it is a contravariant generic delegate.
interface ICovariant<out T> { void DoSomething(Action<T> xCallback); }
The contravariant type (in) can only be used in method arguments and not as return types, it can also be used for generic constraints.
interface IContravariant<in T> { void DoSomething<U>() where U : T; }
There are certain rules about inheritance.
A covariant class cannot inherit from a contravariant class.
A contravariant class cannot inherit from a covariant class.
interface ICovariant<out T> { } interface IContravariant<in T> { } // all ok interface IInvariant1<T> : ICovariant<T> { } interface IInvariant2<T> : IContravariant<T> { } interface IInvariant3<T> : ICovariant<T>, IContravariant<T> { } interface ICovariant1<out T> : ICovariant<T> { } interface IContravariant1<in T> : IContravariant<T> { } // compiler error. //interface ICovariant2<out T> : IContravariant<T> { } //interface ICovariant3<out T> : ICovariant<T>, IContravariant<T> { } //interface IContravariant2<in T> : ICovariant<T> { } //interface IContravariant3<in T> : ICovariant<T>, IContravariant<T> { }
There is no generic type for Arrays. Backward compatibility did not allow an evolution here. Arrays are very basic, they can be used for high-performing applications. If Microsoft would make them more complex, we would somehow lose that benefit. My advice is to use generic collections/Lists instead. You can use T[], but this is rather a strongly typed array than a generic array.
public static void Generics9<T>() { T[] lArray = new T[10]; foreach (T lElement in lArray) { Console.WriteLine(lElement.ToString()); } } //
Reflection (part 1, basics, advanced), types and generic types
I explained generic types yesterday. Discussing reflection is a bit early on this post. I will concentrate on some basic and slightly advanced material about generics and nullables.
Reflection is used to obtain type information at run-time.
C# offers the typeof() keyword for types and the GetType() method for objects.
The is keyword uses both and follows the pattern: object is type
AnyClass c = new AnyClass(); Type a1 = typeof(int); // typeof(type) Type a2 = c.GetType(); // object.GetType() bool b = c is int; // object is type
The is keyword returns true if an instance is in the inheritance tree. This results in a very specific behaviour:
class Animal { } class Cat : Animal { } public static void Generics6() { // easy going Animal lAnimal = new Animal(); Console.WriteLine(lAnimal.GetType() == typeof(Animal)); // true Console.WriteLine(lAnimal is Animal); // true Console.WriteLine(lAnimal.GetType() == typeof(Cat)); // false // and now it gets tricky Animal lCat = new Cat(); Console.WriteLine(lCat.GetType() == typeof(Animal)); // false ! Console.WriteLine(lCat is Animal); // true ! Console.WriteLine(lCat.GetType() == typeof(Cat)); // true } //
The typeof(T) also has its own behaviour. It is does not care what true instance type it is looking at. It returns the result of the last cast operation. Here is an example program to highlight this fact:
static void Print<T>(T t, string xInfo) { Console.WriteLine(xInfo.PadRight(22) + "| typeof(T) " + typeof(T).Name.PadRight(7) + "| GetType(): " + t.GetType().Name.PadRight(7) + "| is Cat: " + (t is Cat).ToString().PadRight(6) + "| is Animal: " + (t is Animal)); } // public static void Confusing() { Animal lAnimal = new Cat(); Cat lCat = new Cat(); Print(lCat, "Print(lCat)"); Print<Cat>(lCat, "Print<Cat>(lCat)"); Print(lAnimal, "Print(lAnimal)"); Print<Animal>(lAnimal, "Print<Animal>(lAnimal)"); Print<Animal>(lCat, "Print<Animal>(lCat)"); Print((Animal)lCat, "Print((Animal)lCat)"); //Print<Cat>(lAnimal); // compiler error } //
example output in a table:
Command typeof(T) GetType() Is Cat? Is Animal? Print(lCat) Cat
Cat
True
True
Print<Cat>(lCat) Cat
Cat
True
True
Print(lAnimal) Animal
Cat
True
True
Print<Animal>(lAnimal) Animal
Cat
True
True
Print<Animal>(lCat) Animal
Cat
True
True
Print((Animal)lCat) Animal
Cat
True
True
Walk through the example of a generic method:
private static void Generics4<T>(T t) { Type lType = typeof(T); Console.WriteLine("Analysing variable " + t + Environment.NewLine); Console.WriteLine("using typeof()"); if (lType == typeof(int)) Console.WriteLine(typeof(T).ToString() + " is an integer"); else if (lType == typeof(string)) Console.WriteLine(typeof(T).ToString() + " is a string"); Console.WriteLine("\nusing the \"is\" keyword"); if (t is int) Console.WriteLine(typeof(T).ToString() + " is an integer"); else if (t is string) Console.WriteLine(typeof(T).ToString() + " is a string"); Console.WriteLine("\nusing GetType()"); if (t.GetType() == typeof(int)) Console.WriteLine(typeof(T).ToString() + " is an integer"); else if (t.GetType() == typeof(string)) Console.WriteLine(typeof(T).ToString() + " is a string"); } // public static void Generics5() { Generics4(5); Console.WriteLine(); Generics4("hello"); Console.WriteLine(); int? i = 666; Generics4(i); Console.ReadLine(); } //
Analysing variable 5
using typeof()
System.Int32 is an integerusing the “is” keyword
System.Int32 is an integerusing GetType()
System.Int32 is an integerAnalysing variable hello
using typeof()
System.String is a stringusing the “is” keyword
System.String is a stringusing GetType()
System.String is a stringAnalysing variable 666
using typeof()
using the “is” keyword
System.Nullable`1[System.Int32] is an integerusing GetType()
System.Nullable`1[System.Int32] is an integer
The Type class offers far more information about the object we are using.
class GenericClass7<T> { public void HelloWorld() { } } private class AnyClass { } public static void Generics7<T>(T t) { Type lType = typeof(T); Console.WriteLine("value".PadRight(30) + t); Console.WriteLine("IsPublic".PadRight(30) + lType.IsPublic); Console.WriteLine("IsGenericParameter".PadRight(30) + lType.IsGenericParameter); Console.WriteLine("IsGenericType".PadRight(30) + lType.IsGenericType); Console.WriteLine("IsGenericTypeDefinition".PadRight(30) + lType.IsGenericTypeDefinition); Console.WriteLine("IsValueType".PadRight(30) + lType.IsValueType); Console.WriteLine("Name".PadRight(30) + lType.Name); Console.WriteLine("BaseTypeName".PadRight(30) + lType.BaseType.Name); foreach (MemberInfo lMember in lType.GetMembers()) { Console.WriteLine("Member".PadRight(30) + lMember.Name); } Console.WriteLine(string.Empty.PadRight(50, '-')); } // public static void RunTest() { Console.WriteLine("integer:\n"); Generics7(1); Console.WriteLine("nullable integer:\n"); Generics7(new Nullable<int>(2)); Console.WriteLine("string:\n"); Generics7("hello"); Console.WriteLine("class:\n"); Generics7(new AnyClass()); Console.WriteLine("generic class:\n"); Generics7(new GenericClass7<double>()); Console.WriteLine(); } //
example output:
integer:
value 1
IsPublic True
IsGenericParameter False
IsGenericType False
IsGenericTypeDefinition False
IsValueType True
Name Int32
BaseTypeName ValueType
Member CompareTo
Member CompareTo
Member Equals
Member Equals
Member GetHashCode
Member ToString
Member ToString
Member ToString
Member ToString
Member Parse
Member Parse
Member Parse
Member Parse
Member TryParse
Member TryParse
Member GetTypeCode
Member GetType
Member MaxValue
Member MinValue
————————————————–
nullable integer:value 2
IsPublic True
IsGenericParameter False
IsGenericType True
IsGenericTypeDefinition False
IsValueType True
Name Nullable`1
BaseTypeName ValueType
Member get_HasValue
Member get_Value
Member GetValueOrDefault
Member GetValueOrDefault
Member Equals
Member GetHashCode
Member ToString
Member op_Implicit
Member op_Explicit
Member GetType
Member .ctor
Member HasValue
Member Value
————————————————–
string:value hello
IsPublic True
IsGenericParameter False
IsGenericType False
IsGenericTypeDefinition False
IsValueType False
Name String
BaseTypeName Object
Member Join
Member Join
Member Join
Member Join
Member Join
Member Equals
Member Equals
Member Equals
Member Equals
Member Equals
Member op_Equality
Member op_Inequality
Member get_Chars
Member CopyTo
Member ToCharArray
Member ToCharArray
Member IsNullOrEmpty
Member IsNullOrWhiteSpace
Member GetHashCode
Member get_Length
Member Split
Member Split
Member Split
Member Split
Member Split
Member Split
Member Substring
Member Substring
Member Trim
Member TrimStart
Member TrimEnd
Member IsNormalized
Member IsNormalized
Member Normalize
Member Normalize
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member Compare
Member CompareTo
Member CompareTo
Member CompareOrdinal
Member CompareOrdinal
Member Contains
Member EndsWith
Member EndsWith
Member EndsWith
Member IndexOf
Member IndexOf
Member IndexOf
Member IndexOfAny
Member IndexOfAny
Member IndexOfAny
Member IndexOf
Member IndexOf
Member IndexOf
Member IndexOf
Member IndexOf
Member IndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOfAny
Member LastIndexOfAny
Member LastIndexOfAny
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member LastIndexOf
Member PadLeft
Member PadLeft
Member PadRight
Member PadRight
Member StartsWith
Member StartsWith
Member StartsWith
Member ToLower
Member ToLower
Member ToLowerInvariant
Member ToUpper
Member ToUpper
Member ToUpperInvariant
Member ToString
Member ToString
Member Clone
Member Trim
Member Insert
Member Replace
Member Replace
Member Remove
Member Remove
Member Format
Member Format
Member Format
Member Format
Member Format
Member Copy
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Concat
Member Intern
Member IsInterned
Member GetTypeCode
Member GetEnumerator
Member GetType
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member .ctor
Member Chars
Member Length
Member Empty
————————————————–
class:value ConsoleApplication1.CGenerics+AnyClass
IsPublic False
IsGenericParameter False
IsGenericType False
IsGenericTypeDefinition False
IsValueType False
Name AnyClass
BaseTypeName Object
Member ToString
Member Equals
Member GetHashCode
Member GetType
Member .ctor
————————————————–
generic class:value ConsoleApplication1.CGenerics+GenericClass7`1[System.Double]
IsPublic False
IsGenericParameter False
IsGenericType True
IsGenericTypeDefinition False
IsValueType False
Name GenericClass7`1
BaseTypeName Object
Member HelloWorld
Member ToString
Member Equals
Member GetHashCode
Member GetType
Member .ctor
————————————————–
I guess there could be hundreds of posts just about reflection. This short explanation should be enough for today. Basically we would start digging ourselves deep into the System.Reflection.Emit namespace. We will touch this topic again when appropriate.
Generic types (part 1, basics, advanced)
Generic types use type parameters instead of hardcoded types. They accept value types/nullables and reference types. In general generics speed up code execution compared to applications prior to C# 2.0. Since then C# code requires much less cast operations. Type safety and code reusability also went up substantially.
When using generics, the code basically does not know the parameter type. Let’s start with a simple example:
public class GenericClass<T> { public void Add(T t) { //AnyClass2 c = new AnyClass2(); //if (t == c) return; // compiler error //if (t != c) return; // compiler error if (t == null) return; // ok return; } } // class private class AnyClass2 { } static void Generics1() { GenericClass<int> a = new GenericClass<int>(); GenericClass<string> b = new GenericClass<string>(); GenericClass<AnyClass2> c = new GenericClass<AnyClass2>(); GenericClass<GenericClass<AnyClass2>> d = new GenericClass<GenericClass<AnyClass2>>(); } //
Above code compiles and works fine. We used value types and reference types. Class GenericClass<T>{} is using an unbounded type parameter.
The parameter type is unclear. Hence the operators “!=” and “==” cannot be used. The support for these operators is unknown.
Nevertheless you can compare t with null, despite the fact that value types cannot be compared with null. In that case the code evaluates “t == null” as false. Value types can never be null. So there is some logic in the unlogic. We will need that behavior from time to time.
Generics can be constrained to particular data types. In my last post on nullable types (https://csharphardcoreprogramming.wordpress.com/2013/12/31/nullable-types-basics/) I quickly introduced “where T: struct”. Here is the complete list (http://msdn.microsoft.com/en-us/library/d5x73970.aspx):
Constraint |
Description |
---|---|
where T: struct | The type argument must be a value type. Any value type except nullable can be specified. |
where T : class | The type argument must be a reference type; this applies also to any class, interface, delegate, or array type. |
where T : new() | The type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last. |
where T : <base class name> | The type argument must be or derive from the specified base class. |
where T : <interface name> | 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 | The type argument supplied for T must be or derive from the argument supplied for U. |
Constraints are important in case you want to use methods that are unknown to the compiler, because your definition is too unreserved. In the next code example the method Test(T t) knows it deals with IEnumerable<string>. Hence we can use “foreach (string s in t)” without any compiler error.
public class GenericClass2 where T : IEnumerable { public void Test(T t) { foreach (string s in t) Console.Write(s); } // } // class static void Generics2() { List lList = (new string[] { "hello", " ", "world" }).ToList(); GenericClass2<List> lGenericClass = new GenericClass2<List>(); lGenericClass.Test(lList); } //
We can go even further and add multiple constraints.
class Falcons<T> where T : Bird, IAnimal, IComparable<T>, new() {} class Cat { } // base class class Tiger<T, U> where U : struct where T : Cat, new() { }
The “where T: U” is quite useless. It does not help the compiler to determine any method. The type is still unknown. Nevertheless you can forward that information if required.
class List<T> { void Add<U>(List<U> items) where U : T {} } // public class SampleClass<T, U, V> where T : V { } //Type parameter V is used as a type constraint.
The default keyword in connection with generics is returning the default value of a parameter. It can be null for objects or eg. 0 for value types.
public class GenericClass3<T> { public void Test() { T lDefaultValue = default(T); bool b = lDefaultValue == null; Console.WriteLine(typeof(T).ToString() + " default value is: " + lDefaultValue + ", is null: " + b); } // } // class static void Generics3() { GenericClass3<int> i = new GenericClass3<int>(); GenericClass3<string> s = new GenericClass3<string>(); i.Test(); s.Test(); } //
example output:
System.Int32 default value is: 0, is null: False
System.String default value is: , is null: True
Nullable types (basics)
You have most likely come across such code:
int? i = 5;
By using “int?”, the integer which usually is a value type, becomes a nullable type. More specifically variable “i” is an instance of System.Nullable<T>. It can hold all values of an integer plus the null value. Nevertheless it behaves more or less like an integer. A nullable type can hold the value null, but not be itself null. Therefore you can still use the variable “i” even though you assign null to it.
Nullable types are very useful when using databases. Database booleans may not be initialized. The System.Nullable<bool> can replicate that properly by allowing 3 values: true, false and null.
static void Main(string[] args) { int? i = 5; Console.WriteLine(i.ToString()); i = null; // important: we assign a null value, we do not assign null to the variable Console.WriteLine("null value ToString(): " + i.ToString()); // the variable itself is not null, no exception Console.WriteLine("no exception thrown!"); //string s = null; //Console.WriteLine(s.ToString()); // throws a NullReferenceException as expected Console.WriteLine(i.HasValue ? "variable i has a value" : "variable i has no value"); i = 7; Console.WriteLine(i.HasValue ? "value of variable i is " + i : "variable i has no value"); i = null; Console.WriteLine("default value is: " + i.GetValueOrDefault()); // i.GetValueOrDefault() returns a value type (=integer) //Console.WriteLine("the integer value is: " + i.Value); // throws an exception, null cannot be unboxed i = 1; int b = 2; Console.WriteLine(b + i + 3); // no problem using operators Console.WriteLine(i.Value); Console.ReadLine(); } //
example output:
5
null value ToString():
no exception thrown!
variable i has no value
value of variable i is 7
default value is: 0
6
A nullable type can only be created for value types. Reference types can hold null already, there is no need for a nullable reference type. The syntax T? is shorthand for Nullable<T>, where T is a value type. The two variations are interchangeable. You can also use the operators == and != eg. if (i == null) i = 1;
In fact the nullable class is not a class. It is a struct. And this explains a lot. A struct is a value type. If supported you do not need the new keyword to create an instance (custom structs do need the new keyword). And when you assign the nullable (=struct) to another variable then you create a copy of that instance. You do not simply copy a reference, which would be much faster. Parameters and locals are created on the stack, members on the heap. As we are talking about value types, we now understand why variables themselves cannot be null. They can hold the value null though (see the following example code). Classes are more complex, you intent to modify them during their use. In contrast structs are best suited for small data which is not intended to be modified after the struct is created.
The nullable struct could look like this:
struct Nullable<T> where T : struct { private readonly bool _HasValue; private readonly T _Value; public Nullable(T xValue) { _HasValue = true; _Value = xValue; } // constructor public bool HasValue { get { return _HasValue; } } // public T Value { get { if (_HasValue) return _Value; throw new ArgumentException(); } } // public T GetValueOrDefault() { return _Value; } // } // struct
The struct “struct Nullable<T> where T : struct {}” is defined with “where T : struct”. This means the argument T has to be a value type (except a nullable, you cannot create nullables of nullables).
We will take a closer look at the “where T:” in the next post.