infuerno.github.io

Pluralsight: C# Generics

Generic Delegates

Regular delegates would require using object and the disadvantages (not type safe, boxing performance hit).

// declare a delegate type
public delegate void Printer(object data);

// declare a method that has the same signature
public void WriteToConsole(object data) {
    Console.WriteLine(data);
}

// create a delegate to point to this method
var consolePrinter = new Printer(WriteToConsole);
// invoke the delegate
consolePrinter.Invoke("hello");
// or pass the delegate to a method that will use it
buffer.Dump(consolePrinter);

// using shorthand (method group conversion)
var consolePrinter = WriteToConsole;
consolePrinter("hello"); // or buffer.Dump(consolePrinter)

Using generics:

// make it all generic
public delegate void Printer<T>(T data);
public void WriteToConsole<T>(T data) {
    Console.WriteLine(data);
}
var consolePrinter = new Printer<double>(WriteToConsole);
consolePrinter.Invoke(2.0); // or buffer.Dump(consolePrinter)

General purpose delegate types

To save having to create your own delegate types, there are several built in delegate types which can be used instead: Func, Action and Predicate.

Action

Always returns void, but can take from 0 to 16 params, the types of which are determined using from 0 to 16 generic type arguments. e.g. Action<double> can be used for a method which returns void, but takes one parameter of type double.

var consolePrinter = new Action<double>(WriteToConsole);
consolePrinter(2.0);
// or if passing to a method, that method would have to be updated to accept a delegate type of Action
buffer.Dump(consolePrinter);

// can use method group conversion instead
var consolePrinter = WriteToConsole;

// can use anonymous method
Action<double> consolePrinter = delegate(double data) { Console.WriteLine(data);};

// can use lamda expression
Action<double> consolePrinter = d => Console.WriteLine(data);
buffer.Dump(consolePrinter);

// can use inline lamda
buffer.Dump(d => d.Console.WriteLine(data));

Func

Always has a return type, which is the last argument specified as well as 0 to 16 parameters e.g. Func<double, double> points to a method which takes a double and returns a double.

Func<double, double> square = d => d * d;
Func<double, double, double> add = (x, y) => x + y;

Predicate

Always returns a boolean.

Predicate<double> isLessThanTen = d => d < 10;

Events and Generics

As well as Func, Action and Predicate the built in delegate EventHandler is useful for raising events.

Come back to this after checking more into events

Constraints, Covariance, Contravariance

Constraints

Use the where keyword to restrict generic types. This means that we know we can now call certain methods in the implementation. It makes more sense to add these restrictions just to the implementations rather than interfaces, since the interface doesn’t care about implementation details. However matter of personal taste.

Covariance

Contravariance

Generics and Reflection

Instantiate Generic Types

Create a generic type using the whole type definition

Type t = typeof(List<Employee>);
object o = Activator.CreateInstance(t);
Console.WriteLine(o.GetType().Name); // List`1
o.GetType().GenericTypeArguments.ToList().ForEach(arg => Console.Write($"[{arg.Name}]")); // [Employee]

Create a generic type where collection type and item type are specified seperately by calling MakeGenericType on the collection type.

Type typeCollection = typeof(List<>);
type typeItem = typeof(Employee);
Type closedType = typeCollection.MakeGenericType(typeItem);
object o = Activator.CreateInstance(closedType);

Invoke Generic Methods

The below code would usually work for non generic methods, but doesn’t work where generic parameters are required.

var employee = new Employee();
var employeeType = employee.GetType();
var methodInfo = employeeType.GetMethod("Speak");
methodInfo.Invoke(employee, null); // null since method doesn't have any parameters

Need to create a generic method using MakeGenericMethod on the method info: methodInfo = methodInfo.MakeGenericMethod(typeof(DateTime));

Odds and Ends

Enums

When using generics with enums - it is not possible to constrain a generic type to be of type enumeration. The nearest possible is of a value type. So e.g. writing a generic method to convert a string to an enumeration cannot be constrained to ensure it will only be used with enumerations.

enum ColourType {
    Black, White
}
public static StringExtensions
{
    public static T ParseEnum<T>(this string value) : where T : struct
    {
        return (T)Enum.Parse(typeof(T), value);
    }
}

var input = "black";
var colour = input.ParseEnum<ColourType>();

Maths

Can’t enforce generic types such that they implement certain operators e.g. +, <, +=. Best approach here is overloaded methods instead.

Using Base Types

If you have a generic class Item<T> and you want to have a list of only these items, you need to defined a strongly type list OF strongly typed items and it wouldn’t be possible to e.g. have Item<int> and Item<double> in the same list.

One solution is to create a Item base type and have Item<T> : Item inherit from this base type. This way you have have a List<Item> and maintain some control over what is added to the list.

Generics and Statics

Static fields belong to the generic type specified FOR a particular type e.g. Item<int> and not shared across all Item<T>.