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

24

465

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 
Illicit answered 28/8, 2008 at 16:4 Comment(3)
There are now various C# proposals that would allow accomplishing this, but AFAIK none of them are further than preliminary explorations/discussions. See Exploration: Shapes and Extensions, Exploration: Roles, extension interfaces and static interface members, Champion "Type Classes (aka Concepts, Structural Generic Constraints)", and Proposal: Generic types should support operatorsKristakristal
As of September 2021, this PR seems to have the most traction and I think it'll be accepted for .NET 7: github.com/dotnet/designs/pull/205Ministry
The future is there devblogs.microsoft.com/dotnet/dotnet-7-generic-mathPossessive
R
16

This constraint exists in .Net 7.

Check out this .NET Blog post and the actual documentation.

Starting in .NET 7, you can make use of interfaces such as INumber and IFloatingPoint to create programs such as:

using System.Numerics;

Console.WriteLine(Sum(1, 2, 3, 4, 5));
Console.WriteLine(Sum(10.541, 2.645));
Console.WriteLine(Sum(1.55f, 5, 9.41f, 7));

static T Sum<T>(params T[] numbers) where T : INumber<T>
{
    T result = T.Zero;

    foreach (T item in numbers)
    {
        result += item;
    }

    return result;
}

INumber is in the System.Numerics namespace.

There are also interfaces such as IAdditionOperators and IComparisonOperators so you can make use of specific operators generically.

Rumrunner answered 12/8, 2021 at 8:0 Comment(1)
@ToddWest updated based on information in article provided, thanks for commentRumrunner
R
202

More than a decade later, this feature finally exists in .NET 7. The most generic interface is INumber<TSelf> (in the System.Numerics namespace), and it encompasses all numbers. To accept just integer types, consider using IBinaryInteger<TSelf> instead.

Here’s an example IntegerFunction implementation:

static bool IntegerFunction<T>(T value) where T : IBinaryInteger<T> {
    return value > T.Zero;
}
Console.WriteLine(IntegerFunction(5));         // True
Console.WriteLine(IntegerFunction((sbyte)-5)); // False
Console.WriteLine(IntegerFunction((ulong)5));  // True

The (now obsolete) original answer below is left as a historical perspective.

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.

Royroyal answered 29/8, 2008 at 8:38 Comment(12)
btw, MiscUtil provides a generic class that does exactly this; Operator/Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htmlGave
@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.Royroyal
@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.Claudianus
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.Confirm
Use decimal for everything and cast back to the type you need.Benjaminbenji
@Benjaminbenji 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.Royroyal
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 : INumberMadalinemadalyn
For that matter, we could add an IPrimitive as well, assign it to all the primitives, thus allowing: where T: IPrimitiveMadalinemadalyn
This is very popular topic, consider updating the answer with devblogs.microsoft.com/dotnet/dotnet-7-generic-mathPossessive
@AlexeiLevenkov Thanks, I finally found time to do this. I have to admit that I initially also misunderstood the article (though in hindsight I don’t understand why) and thought this was not extensible and hard-coded to only predefined numeric types, which would have made it useless. Instead I’m glad to see that .NET once again solved the problem properly, if somewhat late.Royroyal
I'm glad .NET finally got around to adding this! I wanted to point out, however, that INumber<T> is also implemented by the char type. So for folks that want to use INumber<T> as a generic constraint, make sure your method guards against char (unless you want to support it of course).Phonation
Follow-up to my previous comment: Use ISignedNumber<T> instead of INumber<T>. This lets you use numeric types (int, float, double, decimal etc.) while excluding char and other not-numeric types.Phonation
M
125

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.

Matrilineal answered 15/3, 2014 at 14:30 Comment(11)
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.Stative
@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 typesMatrilineal
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?Stative
@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.Matrilineal
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! :)Stative
This workaround using policies #33164 does use T4 to generate classes.Greylag
+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.Magnetron
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.Hued
@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.Greylag
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?Pearl
It is also worth mentioning that T4 is unfortunately not available for .NET Core, cf. github.com/dotnet/runtime/issues/23403Sesquipedalian
F
101

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.

Fragmentary answered 28/8, 2008 at 16:11 Comment(5)
I wonder if you've seen MiscUtil's support for generic operators... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htmlGave
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.Fragmentary
Wait, where T : operators( +, -, /, * ) is legal C#? Sorry for the newbie question.Lodgment
@Lodgment 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.Middlings
This is now a thing in .NET 6: devblogs.microsoft.com/dotnet/…Praefect
G
68

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.

Greylag answered 28/1, 2011 at 23:38 Comment(4)
I had issues with this pattern when there are less function arguments than generic parameters. Opened #36048748Snobbish
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() { }.Cookout
@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 :-) .Greylag
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.Athalie
S
28

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.

Scrim answered 1/2, 2020 at 23:30 Comment(9)
Nice, but not enough... For example, DateTime falls under unmanaged, IComparable, IEquatable<T> constraint..Ingeborg
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 enoughScrim
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.Lorrielorrimer
For most cases this is enough and easy to remember. Improves readability - for those who read code intention is clear.Cunha
Documentation: learn.microsoft.com/en-us/dotnet/csharp/programming-guide/…Ahmed
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.Chlamydeous
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.Scrim
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.Scrim
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.Scrim
G
18

This question is a bit of a FAQ one, so I'm posting this as wiki (since I've posted similar before, but this is an older one); anyway...

What version of .NET are you using? If you are using .NET 3.5, then I have a generic operators implementation in MiscUtil (free etc).

This has methods like T Add<T>(T x, T y), and other variants for arithmetic on different types (like DateTime + TimeSpan).

Additionally, this works for all the inbuilt, lifted and bespoke operators, and caches the delegate for performance.

Some additional background on why this is tricky is here.

You may also want to know that dynamic (4.0) sort-of solves this issue indirectly too - i.e.

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
Gave answered 28/8, 2008 at 16:5 Comment(0)
R
16

Unfortunately you are only able to specify struct in the where clause in this instance. It does seem strange you can't specify Int16, Int32, etc. specifically but I'm sure there's some deep implementation reason underlying the decision to not permit value types in a where clause.

I guess the only solution is to do a runtime check which unfortunately prevents the problem being picked up at compile time. That'd go something like:-

static bool IntegerFunction<T>(T value) where T : struct {
  if (typeof(T) != typeof(Int16)  &&
      typeof(T) != typeof(Int32)  &&
      typeof(T) != typeof(Int64)  &&
      typeof(T) != typeof(UInt16) &&
      typeof(T) != typeof(UInt32) &&
      typeof(T) != typeof(UInt64)) {
    throw new ArgumentException(
      string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
  }

  // Rest of code...
}

Which is a little bit ugly I know, but at least provides the required constraints.

I'd also look into possible performance implications for this implementation, perhaps there's a faster way out there.

Regimen answered 28/8, 2008 at 16:24 Comment(3)
+1, However, // Rest of code... may not compile if it depends on operations defined by the constraints.Phanerogam
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. :-pContinuative
-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.Palp
R
16

This constraint exists in .Net 7.

Check out this .NET Blog post and the actual documentation.

Starting in .NET 7, you can make use of interfaces such as INumber and IFloatingPoint to create programs such as:

using System.Numerics;

Console.WriteLine(Sum(1, 2, 3, 4, 5));
Console.WriteLine(Sum(10.541, 2.645));
Console.WriteLine(Sum(1.55f, 5, 9.41f, 7));

static T Sum<T>(params T[] numbers) where T : INumber<T>
{
    T result = T.Zero;

    foreach (T item in numbers)
    {
        result += item;
    }

    return result;
}

INumber is in the System.Numerics namespace.

There are also interfaces such as IAdditionOperators and IComparisonOperators so you can make use of specific operators generically.

Rumrunner answered 12/8, 2021 at 8:0 Comment(1)
@ToddWest updated based on information in article provided, thanks for commentRumrunner
K
15

Probably the closest you can do is

static bool IntegerFunction<T>(T value) where T: struct

Not sure if you could do the following

static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>

For something so specific, why not just have overloads for each type, the list is so short and it would possibly have less memory footprint.

Kurbash answered 28/8, 2008 at 16:12 Comment(0)
S
11

Topic is old but for future readers:

This feature is tightly related to Discriminated Unions which is not implemented in C# so far. I found its issue here:

https://github.com/dotnet/csharplang/issues/113

This issue is still open and feature has been planned for C# 10

So still we have to wait a bit more, but after releasing you can do it this way:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
Semilunar answered 5/6, 2020 at 8:35 Comment(1)
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.Chlamydeous
C
5

There is no way to restrict templates to types, but you can define different actions based on the type. As part of a generic numeric package, I needed a generic class to add two values.

    class Something<TCell>
    {
        internal static TCell Sum(TCell first, TCell second)
        {
            if (typeof(TCell) == typeof(int))
                return (TCell)((object)(((int)((object)first)) + ((int)((object)second))));

            if (typeof(TCell) == typeof(double))
                return (TCell)((object)(((double)((object)first)) + ((double)((object)second))));

            return second;
        }
    }

Note that the typeofs are evaluated at compile time, so the if statements would be removed by the compiler. The compiler also removes spurious casts. So Something would resolve in the compiler to

        internal static int Sum(int first, int second)
        {
            return first + second;
        }
Conglobate answered 23/12, 2014 at 19:53 Comment(2)
Thank you for providing a empirical solution!Christophe
Is it not the same than creating the same method for each type?Speos
A
4

I created a little library functionality to solve these problems:

Instead of:

public T DifficultCalculation<T>(T a, T b)
{
    T result = a * b + a; // <== WILL NOT COMPILE!
    return result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Should result in 8.

You could write:

public T DifficultCalculation<T>(Number<T> a, Number<T> b)
{
    Number<T> result = a * b + a;
    return (T)result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.

You can find the source code here: https://codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number

Ales answered 10/5, 2013 at 11:15 Comment(0)
N
3

I had a similar situation where I needed to handle numeric types and strings; seems a bit of a bizarre mix but there you go.

Again, like many people I looked at constraints and came up with a bunch of interfaces that it had to support. However, a) it wasn't 100% watertight and b), anyone new looking at this long list of constraints would be immediately very confused.

So, my approach was to put all my logic into a generic method with no constraints, but to make that generic method private. I then exposed it with public methods, one explicitly handling the type I wanted to handle - to my mind, the code is clean and explicit, e.g.

public static string DoSomething(this int input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this decimal input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this double input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this string input, ...) => DoSomethingHelper(input, ...);

private static string DoSomethingHelper<T>(this T input, ....)
{
    // complex logic
}
Nadya answered 10/12, 2018 at 10:6 Comment(0)
W
3

Unfortunately .NET doesn't provide a way to do that natively.

To address this issue I created the OSS library Genumerics which provides most standard numeric operations for the following built-in numeric types and their nullable equivalents with the ability to add support for other numeric types.

sbyte, byte, short, ushort, int, uint, long, ulong, float, double, decimal, and BigInteger

The performance is equivalent to a numeric type specific solution allowing you to create efficient generic numeric algorithms.

Here's an example of the code usage.

public static T Sum(T[] items)
{
    T sum = Number.Zero<T>();
    foreach (T item in items)
    {
        sum = Number.Add(sum, item);
    }
    return sum;
}
public static T SumAlt(T[] items)
{
    // implicit conversion to Number<T>
    Number<T> sum = Number.Zero<T>();
    foreach (T item in items)
    {
        // operator support
        sum += item;
    }
    // implicit conversion to T
    return sum;
}
Wendolyn answered 30/1, 2020 at 17:21 Comment(0)
W
3

.NET 6 has this functionality a preview feature:

https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/#generic-math

An example from the article:

static T Add<T>(T left, T right)
    where T : INumber<T>
{
    return left + right;
}

INumber is an interface that implements other interfaces, such as IAdditionOperators which allows the generic + usage. This is possible now because of another preview feature which is the static abstracts in interfaces, because the + operator overload is a static method:

/// <summary>Defines a mechanism for computing the sum of two values.</summary>
/// <typeparam name="TSelf">The type that implements this interface.</typeparam>
/// <typeparam name="TOther">The type that will be added to <typeparamref name="TSelf" />.</typeparam>
/// <typeparam name="TResult">The type that contains the sum of <typeparamref name="TSelf" /> and <typeparamref name="TOther" />.</typeparam>
[RequiresPreviewFeatures(Number.PreviewFeatureMessage, Url = Number.PreviewFeatureUrl)]
public interface IAdditionOperators<TSelf, TOther, TResult>
    where TSelf : IAdditionOperators<TSelf, TOther, TResult>
{
    /// <summary>Adds two values together to compute their sum.</summary>
    /// <param name="left">The value to which <paramref name="right" /> is added.</param>
    /// <param name="right">The value which is added to <paramref name="left" />.</param>
    /// <returns>The sum of <paramref name="left" /> and <paramref name="right" />.</returns>
    static abstract TResult operator +(TSelf left, TOther right);
}
Wescott answered 20/11, 2021 at 14:13 Comment(1)
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.Shiftless
G
2

I was wondering the same as samjudson, why only to integers? and if that is the case, you might want to create a helper class or something like that to hold all the types you want.

If all you want are integers, don't use a generic, that is not generic; or better yet, reject any other type by checking its type.

Greasepaint answered 28/8, 2008 at 16:14 Comment(0)
H
2

There is no 'good' solution for this yet. However you can narrow the type argument significantly to rule out many missfits for your hypotetical 'INumeric' constraint as Haacked has shown above.

static bool IntegerFunction<T>(T value) where T: IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>, struct {...

Haldi answered 1/7, 2011 at 15:8 Comment(0)
S
2

If you are using .NET 4.0 and later then you can just use dynamic as method argument and check in runtime that the passed dynamic argument type is numeric/integer type.

If the type of the passed dynamic is not numeric/integer type then throw exception.

An example short code that implements the idea is something like:

using System;
public class InvalidArgumentException : Exception
{
    public InvalidArgumentException(string message) : base(message) {}
}
public class InvalidArgumentTypeException : InvalidArgumentException
{
    public InvalidArgumentTypeException(string message) : base(message) {}
}
public class ArgumentTypeNotIntegerException : InvalidArgumentTypeException
{
    public ArgumentTypeNotIntegerException(string message) : base(message) {}
}
public static class Program
{
    private static bool IntegerFunction(dynamic n)
    {
        if (n.GetType() != typeof(Int16) &&
            n.GetType() != typeof(Int32) &&
            n.GetType() != typeof(Int64) &&
            n.GetType() != typeof(UInt16) &&
            n.GetType() != typeof(UInt32) &&
            n.GetType() != typeof(UInt64))
            throw new ArgumentTypeNotIntegerException("argument type is not integer type");
        //code that implements IntegerFunction goes here
    }
    private static void Main()
    {
         Console.WriteLine("{0}",IntegerFunction(0)); //Compiles, no run time error and first line of output buffer is either "True" or "False" depends on the code that implements "Program.IntegerFunction" static method.
         Console.WriteLine("{0}",IntegerFunction("string")); //Also compiles but it is run time error and exception of type "ArgumentTypeNotIntegerException" is thrown here.
         Console.WriteLine("This is the last Console.WriteLine output"); //Never reached and executed due the run time error and the exception thrown on the second line of Program.Main static method.
    }

Of course that this solution works in run time only but never in compile time.

If you want a solution that always works in compile time and never in run time then you will have to wrap the dynamic with a public struct/class whose overloaded public constructors accept arguments of the desired types only and give the struct/class appropriate name.

It makes sense that the wrapped dynamic is always private member of the class/struct and it is the only member of the struct/class and the name of the only member of the struct/class is "value".

You will also have to define and implement public methods and/or operators that work with the desired types for the private dynamic member of the class/struct if necessary.

It also makes sense that the struct/class has special/unique constructor that accepts dynamic as argument that initializes it's only private dynamic member called "value" but the modifier of this constructor is private of course.

Once the class/struct is ready define the argument's type of IntegerFunction to be that class/struct that has been defined.

An example long code that implements the idea is something like:

using System;
public struct Integer
{
    private dynamic value;
    private Integer(dynamic n) { this.value = n; }
    public Integer(Int16 n) { this.value = n; }
    public Integer(Int32 n) { this.value = n; }
    public Integer(Int64 n) { this.value = n; }
    public Integer(UInt16 n) { this.value = n; }
    public Integer(UInt32 n) { this.value = n; }
    public Integer(UInt64 n) { this.value = n; }
    public Integer(Integer n) { this.value = n.value; }
    public static implicit operator Int16(Integer n) { return n.value; }
    public static implicit operator Int32(Integer n) { return n.value; }
    public static implicit operator Int64(Integer n) { return n.value; }
    public static implicit operator UInt16(Integer n) { return n.value; }
    public static implicit operator UInt32(Integer n) { return n.value; }
    public static implicit operator UInt64(Integer n) { return n.value; }
    public static Integer operator +(Integer x, Int16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int64 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt64 y) { return new Integer(x.value + y); }
    public static Integer operator -(Integer x, Int16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int64 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt64 y) { return new Integer(x.value - y); }
    public static Integer operator *(Integer x, Int16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int64 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt64 y) { return new Integer(x.value * y); }
    public static Integer operator /(Integer x, Int16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int64 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt64 y) { return new Integer(x.value / y); }
    public static Integer operator %(Integer x, Int16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int64 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt64 y) { return new Integer(x.value % y); }
    public static Integer operator +(Integer x, Integer y) { return new Integer(x.value + y.value); }
    public static Integer operator -(Integer x, Integer y) { return new Integer(x.value - y.value); }
    public static Integer operator *(Integer x, Integer y) { return new Integer(x.value * y.value); }
    public static Integer operator /(Integer x, Integer y) { return new Integer(x.value / y.value); }
    public static Integer operator %(Integer x, Integer y) { return new Integer(x.value % y.value); }
    public static bool operator ==(Integer x, Int16 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int16 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int32 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int32 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int64 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int64 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt16 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt16 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt32 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt32 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt64 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt64 y) { return x.value != y; }
    public static bool operator ==(Integer x, Integer y) { return x.value == y.value; }
    public static bool operator !=(Integer x, Integer y) { return x.value != y.value; }
    public override bool Equals(object obj) { return this == (Integer)obj; }
    public override int GetHashCode() { return this.value.GetHashCode(); }
    public override string ToString() { return this.value.ToString(); }
    public static bool operator >(Integer x, Int16 y) { return x.value > y; }
    public static bool operator <(Integer x, Int16 y) { return x.value < y; }
    public static bool operator >(Integer x, Int32 y) { return x.value > y; }
    public static bool operator <(Integer x, Int32 y) { return x.value < y; }
    public static bool operator >(Integer x, Int64 y) { return x.value > y; }
    public static bool operator <(Integer x, Int64 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt16 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt16 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt32 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt32 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt64 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt64 y) { return x.value < y; }
    public static bool operator >(Integer x, Integer y) { return x.value > y.value; }
    public static bool operator <(Integer x, Integer y) { return x.value < y.value; }
    public static bool operator >=(Integer x, Int16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Integer y) { return x.value >= y.value; }
    public static bool operator <=(Integer x, Integer y) { return x.value <= y.value; }
    public static Integer operator +(Int16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator -(Int16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator *(Int16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator /(Int16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator %(Int16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int64 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt64 x, Integer y) { return new Integer(x % y.value); }
    public static bool operator ==(Int16 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int16 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int32 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int32 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int64 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int64 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt16 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt16 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt32 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt32 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt64 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt64 x, Integer y) { return x != y.value; }
    public static bool operator >(Int16 x, Integer y) { return x > y.value; }
    public static bool operator <(Int16 x, Integer y) { return x < y.value; }
    public static bool operator >(Int32 x, Integer y) { return x > y.value; }
    public static bool operator <(Int32 x, Integer y) { return x < y.value; }
    public static bool operator >(Int64 x, Integer y) { return x > y.value; }
    public static bool operator <(Int64 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt16 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt16 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt32 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt32 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt64 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt64 x, Integer y) { return x < y.value; }
    public static bool operator >=(Int16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int64 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt64 x, Integer y) { return x <= y.value; }
}
public static class Program
{
    private static bool IntegerFunction(Integer n)
    {
        //code that implements IntegerFunction goes here
        //note that there is NO code that checks the type of n in rum time, because it is NOT needed anymore 
    }
    private static void Main()
    {
        Console.WriteLine("{0}",IntegerFunction(0)); //compile error: there is no overloaded METHOD for objects of type "int" and no implicit conversion from any object, including "int", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer(0))); //both compiles and no run time error
        Console.WriteLine("{0}",IntegerFunction("string")); //compile error: there is no overloaded METHOD for objects of type "string" and no implicit conversion from any object, including "string", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer("string"))); //compile error: there is no overloaded CONSTRUCTOR for objects of type "string"
    }
}

Note that in order to use dynamic in your code you must Add Reference to Microsoft.CSharp

If the version of the .NET framework is below/under/lesser than 4.0 and dynamic is undefined in that version then you will have to use object instead and do casting to the integer type, which is trouble, so I recommend that you use at least .NET 4.0 or newer if you can so you can use dynamic instead of object.

Scribbler answered 19/8, 2017 at 19:23 Comment(0)
S
1

What is the point of the exercise?

As people pointed out already, you could have a non-generic function taking the largest item, and compiler will automatically convert up smaller ints for you.

static bool IntegerFunction(Int64 value) { }

If your function is on performance-critical path (very unlikely, IMO), you could provide overloads for all needed functions.

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
Stenotypy answered 28/8, 2008 at 20:59 Comment(1)
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.Graiae
I
1

I would use a generic one which you could handle externaly...

/// <summary>
/// Generic object copy of the same type
/// </summary>
/// <typeparam name="T">The type of object to copy</typeparam>
/// <param name="ObjectSource">The source object to copy</param>
public T CopyObject<T>(T ObjectSource)
{
    T NewObject = System.Activator.CreateInstance<T>();

    foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
        NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);

    return NewObject;
}
Imperfect answered 14/1, 2010 at 15:53 Comment(0)
H
1

This limitation affected me when I tried to overload operators for generic types; since there was no "INumeric" constraint, and for a bevy of other reasons the good people on stackoverflow are happy to provide, operations cannot be defined on generic types.

I wanted something like

public struct Foo<T>
{
    public T Value{ get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + RHS.Value; };
    }
}

I have worked around this issue using .net4 dynamic runtime typing.

public struct Foo<T>
{
    public T Value { get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + (dynamic)RHS.Value };
    }
}

The two things about using dynamic are

  1. Performance. All value types get boxed.
  2. Runtime errors. You "beat" the compiler, but lose type safety. If the generic type doesn't have the operator defined, an exception will be thrown during execution.
Hupp answered 15/11, 2010 at 20:52 Comment(0)
M
1

The .NET numeric primitive types do not share any common interface that would allow them to be used for calculations. It would be possible to define your own interfaces (e.g. ISignedWholeNumber) which would perform such operations, define structures which contain a single Int16, Int32, etc. and implement those interfaces, and then have methods which accept generic types constrained to ISignedWholeNumber, but having to convert numeric values to your structure types would likely be a nuisance.

An alternative approach would be to define static class Int64Converter<T> with a static property bool Available {get;}; and static delegates for Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest). The class constructor could use be hard-coded to load delegates for known types, and possibly use Reflection to test whether type T implements methods with the proper names and signatures (in case it's something like a struct which contains an Int64 and represents a number, but has a custom ToString() method). This approach would lose the advantages associated with compile-time type-checking, but would still manage to avoid boxing operations and each type would only have to be "checked" once. After that, operations associated with that type would be replaced with a delegate dispatch.

Marikomaril answered 6/5, 2013 at 15:44 Comment(1)
@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.Marikomaril
P
0

If all you want is use one numeric type, you could consider creating something similar to an alias in C++ with using.

So instead of having the very generic

T ComputeSomething<T>(T value1, T value2) where T : INumeric { ... }

you could have

using MyNumType = System.Double;
T ComputeSomething<MyNumType>(MyNumType value1, MyNumType value2) { ... }

That might allow you to easily go from double to int or others if needed, but you wouldn't be able to use ComputeSomething with double and int in the same program.

But why not replace all double to int then? Because your method may want to use a double whether the input is double or int. The alias allows you to know exactly which variable uses the dynamic type.

Patisserie answered 7/12, 2018 at 5:49 Comment(0)
P
0

All numeric types are structs which implement IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable. However, so does DateTime.

So this generic extension method is possible:

public static bool IsNumeric<T>(this T value) where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable =>
  typeof(T) != typeof(DateTime);

But it will fail for a struct that implements those interfaces, e.g.:

public struct Foo : IComparable, IComparable<Foo>, IConvertible, IEquatable<Foo>, IFormattable { /* ... */ }

This non-generic alternative is less performant, but guaranteed to work:

public static bool IsNumeric(this Type type) =>
  type == typeof(sbyte) || type == typeof(byte) ||
  type == typeof(short) || type == typeof(ushort) ||
  type == typeof(int) || type == typeof(uint) ||
  type == typeof(long) || type == typeof(ulong) ||
  type == typeof(float) ||
  type == typeof(double) ||
  type == typeof(decimal);
Pepys answered 22/9, 2021 at 17:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.