Is there a constraint that restricts my generic method to numeric types?

133,580

Solution 1

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 that Matrix you would like to define a dot product method. That of course that means you ultimately need to understand how to multiply two Ts, but you can't say that as a constraint, at least not if T is int, double, or float. But what you could do is have your Matrix take as an argument a Calculator<T>, and in Calculator<T>, have a method called multiply. You go implement that and you pass it to the Matrix.

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.

Solution 2

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:

  • Create a new Text Template file called GenericNumberMethodTemplate.tt.
  • Remove the auto-generated code (you'll keep most of it, but some isn't needed).
  • Add the following snippet:
<#@ 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();
        }
    }
}

enter image description here

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.

Solution 3

There's no constraint for this. It's a real issue for anyone wanting to use generics for numeric calculations.

I'd go further and say we need

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )

Or even

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract

Unfortunately you only have interfaces, base classes and the keywords struct (must be value-type), class (must be reference type) and new() (must have default constructor)

You could wrap the number in something else (similar to INullable<T>) like here on codeproject.


You could apply the restriction at runtime (by reflecting for the operators or checking for types) but that does lose the advantage of having the generic in the first place.

Solution 4

Workaround using policies:

interface INumericPolicy<T>
{
    T Zero();
    T Add(T a, T b);
    // add more functions here, such as multiplication etc.
}

struct NumericPolicies:
    INumericPolicy<int>,
    INumericPolicy<long>
    // add more INumericPolicy<> for different numeric types.
{
    int INumericPolicy<int>.Zero() { return 0; }
    long INumericPolicy<long>.Zero() { return 0; }
    int INumericPolicy<int>.Add(int a, int b) { return a + b; }
    long INumericPolicy<long>.Add(long a, long b) { return a + b; }
    // implement all functions from INumericPolicy<> interfaces.

    public static NumericPolicies Instance = new NumericPolicies();
}

Algorithms:

static class Algorithms
{
    public static T Sum<P, T>(this P p, params T[] a)
        where P: INumericPolicy<T>
    {
        var r = p.Zero();
        foreach(var i in a)
        {
            r = p.Add(r, i);
        }
        return r;
    }

}

Usage:

int i = NumericPolicies.Instance.Sum(1, 2, 3, 4, 5);
long l = NumericPolicies.Instance.Sum(1L, 2, 3, 4, 5);
NumericPolicies.Instance.Sum("www", "") // compile-time error.

The solution is compile-time safe. CityLizard Framework provides compiled version for .NET 4.0. The file is lib/NETFramework4.0/CityLizard.Policy.dll.

It's also available in Nuget: https://www.nuget.org/packages/CityLizard/. See CityLizard.Policy.I structure.

Solution 5

Beginning with C# 7.3, you can use closer approximation - the unmanaged constraint to specify that a type parameter is a non-pointer, non-nullable unmanaged type.

class SomeGeneric<T> where T : unmanaged
{
//...
}

The unmanaged constraint implies the struct constraint and can't be combined with either the struct or new() constraints.

A type is an unmanaged type if it's any of the following types:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool
  • Any enum type
  • Any pointer type
  • Any user-defined struct type that contains fields of unmanaged types only and, in C# 7.3 and earlier, is not a constructed type (a type that includes at least one type argument)

To restrict further and eliminate pointer and user-defined types that do not implement IComparable add IComparable (but enum is still derived from IComparable, so restrict enum by adding IEquatable < T >, you can go further depending on your circumstances and add additional interfaces. unmanaged allows to keep this list shorter):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }

But this doesn't prevent from DateTime instantiation.

Share:
133,580
Corin Blaikie
Author by

Corin Blaikie

C# / Ruby / C++ Developer

Updated on November 21, 2021

Comments

  • Corin Blaikie
    Corin Blaikie over 2 years

    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 
    
  • Marc Gravell
    Marc Gravell over 14 years
    I wonder if you've seen MiscUtil's support for generic operators... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htm‌​l
  • Marc Gravell
    Marc Gravell over 14 years
    btw, MiscUtil provides a generic class that does exactly this; Operator/Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htm‌​l
  • Konrad Rudolph
    Konrad Rudolph over 14 years
    @Mark: good comment. However, just to be clear, I don't think that Hejlsberg was referring to code generation as a solution to the problem as you do in the Operator<T> code (since the interview was given long before the existence of the Expressions framework, even though one could of course use Reflection.Emit) – and I'd be really interested in his workaround.
  • Keith
    Keith over 14 years
    Yeah - Jon Skeet pointed me at them for something else a while ago (but after this year old response) - they're a clever idea, but I'd still like proper constraint support.
  • Ergwun
    Ergwun almost 13 years
    @Konrad Rudolph: I think this answer to a similar question explains Hejlsberg's workaround. The other generic class is made abstract. Since it requires you to implement the other generic class for each type you want to support, it will result in duplicate code, but does mean you can only instantiate the original generic class with a supported type.
  • Sebastian Mach
    Sebastian Mach almost 13 years
    I do not agree to Heijsberg's phrase "So in a sense, C++ templates are actually untyped, or loosely typed. Whereas C# generics are strongly typed. ". That's really Marketing BS to promote C#. Strong/Weak-typing has not to do with quality of diagnostics. Otherwise: Interesting find.
  • Nick
    Nick over 11 years
    +1, However, // Rest of code... may not compile if it depends on operations defined by the constraints.
  • supercat
    supercat over 10 years
    @KenKin: IConvertible provides a means by which any integer could be added to another integer type to yield e.g. an Int64 result, but does not provide a means by which e.g. an integer of arbitrary type could be incremented to yield another integer of the same type.
  • Zachary Kniebel
    Zachary Kniebel about 10 years
    This is pretty cool, but would it be possible to modify this solution to make the methods accept some generic type T that is or inherits from the various IntX classes? I like this solution because it saves time, but for it to 100% solve the issue (despite not being as nice as if C# had support for this type of constraint, built-in) each of the generated methods should still be generic so that they can return an object of a type that inherits from one of the IntXX classes.
  • Jeroen Vannevel
    Jeroen Vannevel about 10 years
    @ZacharyKniebel: the entire idea behind this solution is that you don't create a generic type. What a generic method does behind the scenes is (roughly) creating a method exactly like this: it replaces the generic parameter with the actual type and uses that. A generic method would want to impose constraints on the type passed into it to numeric types and then create actual implementations with the concrete type. Here we skip that part and immediately create the concrete types: all you have to do is add the types you want to your list in the template file and let it generate the concrete types
  • Zachary Kniebel
    Zachary Kniebel about 10 years
    I'm nearing the limit of my knowledge of how generic types are compiled, so I apologize if I am mistaken, but if a generic type generates all of these methods behind the scenes, then mustn't it do so by finding all classes that inherit from the constrained types, and generating a similar method for each? In other words, wouldn't it look for all classes that inherit from each of the IntXX types? If I am correct, then wouldn't your solution fall short of the desired solution as it does not, in its current state, account for the inherited types?
  • Jeroen Vannevel
    Jeroen Vannevel about 10 years
    @ZacharyKniebel: the IntXX types are structs which means they don't support inheritance in the first place. And even if it did then the Liskov substitution principle (which you might know from the SOLID idiom) applies: if the method is defined as X and Y is a child of X then per definition any Y should be able to be passed to that method as a substitute of its base type.
  • Zachary Kniebel
    Zachary Kniebel about 10 years
    I am familiar with the LSP, but that brings the limitations of up-casting vs. down-casting into the mix, which is what I was thinking about when I made the suggestion (as the object returned would be of the inherited type and not the subtype). However, you are 100% correct in that I completely disregarded the fact that the IntXX types are all structs and thus cannot be inherited, so my concern was irrelevant. Great answer, Jeroen, and thanks for all the info! :)
  • Sergey Shandar
    Sergey Shandar about 10 years
    This workaround using policies stackoverflow.com/questions/32664/… does use T4 to generate classes.
  • yoyo
    yoyo almost 10 years
    Convert.ToIntXX(value) might help make "// Rest of code" compile -- at least until the return type of IntegerFunction is also of type T, then you're hooped. :-p
  • VoteCoffee
    VoteCoffee over 9 years
    I work with numerical methods a lot. Sometimes I want integers and sometimes I want floating point. Both have 64 bit versions which are optimal for processing speed. Converting between these is a terrible idea as there are losses each way. While I tend towards using doubles, sometimes I do find it better to use integers because of how they get used elsewhere. But it would be very nice when I am writing an algorithm to do it once and leave the type decision up to the instance requirements.
  • kdbanman
    kdbanman almost 9 years
    Wait, where T : operators( +, -, /, * ) is legal C#? Sorry for the newbie question.
  • zsf222
    zsf222 over 8 years
    Thank you for providing a empirical solution!
  • Attila Klenik
    Attila Klenik about 8 years
    +1 for this solution since it preserves the operation efficiency of the built-in integral types, unlike the policy based solutions. Calling built-in CLR operators (like Add) through an additional (possibly virtual) method can severely affect performance if used many times (like in mathematical libraries). And since the number of integral types is constant (and can't be inherited from) you only need to regenerate the code for bug fixes.
  • xvan
    xvan about 8 years
    I had issues with this pattern when there are less function arguments than generic parameters. Opened stackoverflow.com/questions/36048248/…
  • bradgonesurfing
    bradgonesurfing about 8 years
    Very cool and I was just about to start using it then I remembered how dependent on Resharper I am for refactoring and you can't do rename refactor through the T4 template. It's not critical but worth considering.
  • Sergey Shandar
    Sergey Shandar over 7 years
    @AttilaKlenik Because it's code generation, you may generate both static functions and policies. Static functions may be faster but they can't be used in generic algorithms.
  • M.kazem Akhgary
    M.kazem Akhgary over 7 years
    any reason why using struct? what if I use singleton-class instead and change instance to public static NumericPolicies Instance = new NumericPolicies(); and then add this constructor private NumericPolicies() { }.
  • Sergey Shandar
    Sergey Shandar over 7 years
    @M.kazemAkhgary you may use the singleton. I do prefer struct. In theory, it can be optimized by compiler/CLR because the struct contains no information. In case of singleton, you will still pass a reference, which may add additional pressure on GC. Another advantage is that struct can't be null :-) .
  • Mark Amery
    Mark Amery over 6 years
    -1; this doesn't work for the reason given by @Nick. The moment you try to do any arithmetic operations in // Rest of code... like value + value or value * value, you've got a compile error.
  • Tobias Knauss
    Tobias Knauss almost 6 years
    I was going to say that you found a very smart solution, but the solution is too limited for me: I was going to use it in T Add<T> (T t1, T t2), but Sum() only works when it can retrieve it's own type of T from it's parameters, which is not possible when it's embedded in another generic function.
  • Luis
    Luis over 5 years
    Is it not the same than creating the same method for each type?
  • Alexander Terp
    Alexander Terp about 4 years
    @kdbanman I don't think so. Keith is saying that C# doesn't support what OP is asking, and is suggesting that we should be able to do where T : operators( +, -, /, * ), but can't.
  • Adam Calvet Bohl
    Adam Calvet Bohl about 4 years
    Nice, but not enough... For example, DateTime falls under unmanaged, IComparable, IEquatable<T> constraint..
  • Vlad
    Vlad almost 4 years
    I know but you can go further depending on your circumstances and add additional interfaces. unmanaged allows to keep this list shorter. i have just shown the approach , approximation using unmanaged. For most cases this is enough
  • shelbypereira
    shelbypereira almost 4 years
    this approach will fail in the important cases where we are implementing math operations T Mult<T>(T a,T b){ return a*b;} can only work work for numeric types not for DateTime.
  • jjxtra
    jjxtra almost 4 years
    Use decimal for everything and cast back to the type you need.
  • Konrad Rudolph
    Konrad Rudolph almost 4 years
    @jjxtra That isn’t a terrible solution but in many cases (and probably in most where it matters) it causes a nontrivial, and potentially prohibitive, performance degradation.
  • toughQuestions
    toughQuestions over 3 years
    That is an awesome feature. Is there a way I could use this functionality for the following purpose: I need to go through a bunch files and extract all the fields from each of these files into a different class?
  • SommerEngineering
    SommerEngineering over 3 years
    It is also worth mentioning that T4 is unfortunately not available for .NET Core, cf. github.com/dotnet/runtime/issues/23403
  • Admin
    Admin over 3 years
    For most cases this is enough and easy to remember. Improves readability - for those who read code intention is clear.
  • Luke Vanzweden
    Luke Vanzweden over 2 years
  • blenderfreaky
    blenderfreaky over 2 years
    This is now a thing in .NET 6: devblogs.microsoft.com/dotnet/…
  • ToolmakerSteve
    ToolmakerSteve over 2 years
    While interesting to know about, as shelby mentioned, this doesn't help the goal of being able to use math operations (e.g. +). It doesn't matter if you successfully constrain the typed down to a small enough set - the compiler still won't know that those types all implement the math operators.
  • ToolmakerSteve
    ToolmakerSteve over 2 years
    I don't see the connection. Unions (whether "discriminated" or not) are about passing around a value that is allowed to contain one of several types. Generic constraints are about calling a method that is known to be supported by a type. The relevant proposal is Updating generic math .. for .NET 7. The difference is that when you call a generic method, it is known what type you have. There is no need for a union. For example, in any class, write public static T MyFunc(T a, T b) { ... }. At compile time T gets resolved to a specific type.
  • Vlad
    Vlad over 2 years
    What math operations? How about % (modulus div) or division - should compiler restrict integer division or real numbers division? Frankly speaking the question is not well formulated. Likely concern of author was to allow only integer arithmetic operations (for integer numbers) (+,-,*,/,%) - he enumerated integers only.
  • Vlad
    Vlad over 2 years
    For real numbers inverse operation to multiplication exists and / has another meaning and may return real type with integer operand. As you can see I mentioned in the very beginning that my answer is approximation (programmer has something to start tuning for his own needs) - if language had proper feature - I would give exact answer. Developer of library should spend time for more appropriate solution.
  • Vlad
    Vlad over 2 years
    I believe that most direct way to solve this question is to have possibility to enumerate allowed operators explicitly. At least all algebraic structures are defined this way: domain of values and list of supported operations: groups define addition, rings or fields define addition and multiplication. To reach the goal developer of library should define concrete algebraic structure type and enumerate operations. For applications this my answer is sufficient for me often. In numeric library I'd not rely on fact(maybe temporal) that set of unmanaged types very similar to set of arithmetic types.
  • Artem Vertiy
    Artem Vertiy over 2 years
    I'm wondered why they still in .NET 5+ didn't mark int16, int32, int64 etc as IInteger? uint16,uint32 etc. as IUInteger just to easily put a constraint in generics where integer type is expected regardless its size. also they could add there some type aka dynamic but for primitives only that will allow to void generics at all in some cases.
  • Rhyous
    Rhyous over 2 years
    So I read through this. Just baffled. This could be fixed in the next version of .Net Core in about 1 minute with an INumber interface, which could be blank, but it probably should have things like Parse and TryParse in it, then apply it to all the number primitives. Then all someone has to do to enforce that T is a number is: where T : INumber
  • Rhyous
    Rhyous over 2 years
    For that matter, we could add an IPrimitive as well, assign it to all the primitives, thus allowing: where T: IPrimitive