Generics
Generics overview
Generics enable writing of code abstractions which can be applied to different types at compile time. List
Methods can also have generic parameters, allowing for them to be specialized either explicitly or implicitly based on callsite argument types.
public static T GetFirst<T>(List<T> list)
{
return list[0];
}
...
let intList = new List<int32>();
intList.Add(123);
let firstVal = GetFirst(intList);
Generic constraints can be specified, which describe the ‘shape’ of the type which the generic code is intended to work with.
- Interface type - any number of interfaces can be specified for a generic parameter. The incoming type must declare implementations for all these interfaces.
- Class/struct type - a single concrete type can be specified, which the incoming type must derive from.
- Delegate type - the incoming type can either be an instance of this delegate type, or it can be a method reference whose signature conforms to the delegate (see Method References)
operator T <op> T2
- type must result from binary operation between the specified typesoperator <op> T
- type must result from unary operation on the specifiedoperator implicit T
- type must be implicitly convertible from the specified typeoperator explicit T
- type must be explicitly convertible from the specified typeclass
- type must be classstruct
- type must be a value typeenum
- type must be an enuminterface
- type must be an interfacestruct*
- type must be a pointer to a value typenew
- type must define an accessible default constructordelete
- type must define an accessible destructorconst
- type must be a constant value - see “Const Generics”var
- type is unconstrained. This can be useful for certain kinds of “duck typing”, and can generate patterns similar to C++ templates, but in general produces less useful errors and a less pleasant development experience
public static T Abs<T>(T value) where T : IOpComparable, IOpNegatable
{
if (value < default)
return -value;
else
return value;
}
/* This method can eliminate runtime branching by specializing at compile time by incoming array size */
public static float GetSum<TCount>(float[TCount] vals) where TCount : const int
{
if (vals.Count == 0)
{
return 0;
}
else if (vals.Count == 1)
{
return vals[0];
}
else
{
float total = 0;
for (let val in vals)
total += val;
return total;
}
}
static TTo Convert<TTo, TFrom>(TFrom val) where TTo : operator explicit TFrom
{
return (TTo)val;
}
/* We use partial explicit generic args to allow inference of 'TFrom' */
var val = Convert<int...>(1.2f);
Generic constructors are supported.
class WriteValue
{
public this<T>(T val)
{
Console.WriteLine($"Value: {val}");
}
}
/* Implicitly determine 'T' for constructor */
var val = scope WriteVal(1.0f);
/* Explicitly specify 'T' for constructor */
var val = scope WriteVal.this<float>(1);