Can anyone tell me if there is a way with generics to limit a generic type argument T
to only:
Int16
Int32
Int64
UInt16
UInt32
UInt64
I'm aware of the where
keyword, but can't find an interface for only these types,
Something like:
static bool IntegerFunction<T>(T value) where T : INumeric
Declaring those constraints means you can use the operations and method calls of the constraining type. If your generic class or method uses any operation on the generic members beyond simple assignment or calling any methods not supported by System. Object, you'll apply constraints to the type parameter.
The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.
You can specify one or more constraints on the generic type using the where clause after the generic type name. The following example demonstrates a generic class with a constraint to reference types when instantiating the generic class.
So here is the list of some of the constraints that you can add to the generic classes, using the where keyword: Restrict the generic class to use the type parameter of value or reference type only (as we discussed above). Restrict the type parameter T, to be implementing the specified interface.
C# does not support this. Hejlsberg has described the reasons for not implementing the feature in an interview with Bruce Eckel:
And it's not clear that the added complexity is worth the small yield that you get. If something you want to do is not directly supported in the constraint system, you can do it with a factory pattern. You could have a
Matrix<T>
, for example, and in thatMatrix
you would like to define a dot product method. That of course that means you ultimately need to understand how to multiply twoT
s, but you can't say that as a constraint, at least not ifT
isint
,double
, orfloat
. But what you could do is have yourMatrix
take as an argument aCalculator<T>
, and inCalculator<T>
, have a method calledmultiply
. You go implement that and you pass it to theMatrix
.
However, this leads to fairly convoluted code, where the user has to supply their own Calculator<T>
implementation, for each T
that they want to use. As long as it doesn’t have to be extensible, i.e. if you just want to support a fixed number of types, such as int
and double
, you can get away with a relatively simple interface:
var mat = new Matrix<int>(w, h);
(Minimal implementation in a GitHub Gist.)
However, as soon as you want the user to be able to supply their own, custom types, you need to open up this implementation so that the user can supply their own Calculator
instances. For instance, to instantiate a matrix that uses a custom decimal floating point implementation, DFP
, you’d have to write this code:
var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);
… and implement all the members for DfpCalculator : ICalculator<DFP>
.
An alternative, which unfortunately shares the same limitations, is to work with policy classes, as discussed in Sergey Shandar’s answer.
Considering the popularity of this question and the interest behind such a function I am surprised to see that there is no answer involving T4 yet.
In this sample code I will demonstrate a very simple example of how you can use the powerful templating engine to do what the compiler pretty much does behind the scenes with generics.
Instead of going through hoops and sacrificing compile-time certainty you can simply generate the function you want for every type you like and use that accordingly (at compile time!).
In order to do this:
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<# Type[] types = new[] {
typeof(Int16), typeof(Int32), typeof(Int64),
typeof(UInt16), typeof(UInt32), typeof(UInt64)
};
#>
using System;
public static class MaxMath {
<# foreach (var type in types) {
#>
public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
return val1 > val2 ? val1 : val2;
}
<#
} #>
}
That's it. You're done now.
Saving this file will automatically compile it to this source file:
using System;
public static class MaxMath {
public static Int16 Max (Int16 val1, Int16 val2) {
return val1 > val2 ? val1 : val2;
}
public static Int32 Max (Int32 val1, Int32 val2) {
return val1 > val2 ? val1 : val2;
}
public static Int64 Max (Int64 val1, Int64 val2) {
return val1 > val2 ? val1 : val2;
}
public static UInt16 Max (UInt16 val1, UInt16 val2) {
return val1 > val2 ? val1 : val2;
}
public static UInt32 Max (UInt32 val1, UInt32 val2) {
return val1 > val2 ? val1 : val2;
}
public static UInt64 Max (UInt64 val1, UInt64 val2) {
return val1 > val2 ? val1 : val2;
}
}
In your main
method you can verify that you have compile-time certainty:
namespace TTTTTest
{
class Program
{
static void Main(string[] args)
{
long val1 = 5L;
long val2 = 10L;
Console.WriteLine(MaxMath.Max(val1, val2));
Console.Read();
}
}
}
I'll get ahead of one remark: no, this is not a violation of the DRY principle. The DRY principle is there to prevent people from duplicating code in multiple places that would cause the application to become hard to maintain.
This is not at all the case here: if you want a change then you can just change the template (a single source for all your generation!) and it's done.
In order to use it with your own custom definitions, add a namespace declaration (make sure it's the same one as the one where you'll define your own implementation) to your generated code and mark the class as partial
. Afterwards, add these lines to your template file so it will be included in the eventual compilation:
<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>
Let's be honest: This is pretty cool.
Disclaimer: this sample has been heavily influenced by Metaprogramming in .NET by Kevin Hazzard and Jason Bock, Manning Publications.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With