Is there a way to determine whether or not a given .Net Type is a number? For example: System.UInt32/UInt16/Double are all numbers. I want to avoid a long switch-case on the Type.FullName.
Is there a way to determine whether or not a given .Net Type is a number? For example: System.UInt32/UInt16/Double are all numbers. I want to avoid a long switch-case on the Type.FullName.
With C# 7 this method gives to me better performance than switch case on TypeCode and HashSet<Type>:
public static bool IsNumeric(this object o) => o is byte || o is sbyte || o is ushort || o is uint || o is ulong || o is short || o is int || o is long || o is float || o is double || o is decimal;
Tests are following:
public static class Extensions
{
public static HashSet<Type> NumericTypes = new HashSet<Type>()
{
typeof(byte), typeof(sbyte), typeof(ushort), typeof(uint), typeof(ulong), typeof(short), typeof(int), typeof(long), typeof(decimal), typeof(double), typeof(float)
};
public static bool IsNumeric1(this object o) => NumericTypes.Contains(o.GetType());
public static bool IsNumeric2(this object o) => o is byte || o is sbyte || o is ushort || o is uint || o is ulong || o is short || o is int || o is long || o is decimal || o is double || o is float;
public static bool IsNumeric3(this object o)
{
switch (o)
{
case Byte b:
case SByte sb:
case UInt16 u16:
case UInt32 u32:
case UInt64 u64:
case Int16 i16:
case Int32 i32:
case Int64 i64:
case Decimal m:
case Double d:
case Single f:
return true;
default:
return false;
}
}
public static bool IsNumeric4(this object o)
{
switch (Type.GetTypeCode(o.GetType()))
{
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return true;
default:
return false;
}
}
}
class Program
{
static void Main(string[] args)
{
var count = 100000000;
//warm up calls
for (var i = 0; i < count; i++)
{
i.IsNumeric1();
}
for (var i = 0; i < count; i++)
{
i.IsNumeric2();
}
for (var i = 0; i < count; i++)
{
i.IsNumeric3();
}
for (var i = 0; i < count; i++)
{
i.IsNumeric4();
}
//Tests begin here
var sw = new Stopwatch();
sw.Restart();
for (var i = 0; i < count; i++)
{
i.IsNumeric1();
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Restart();
for (var i = 0; i < count; i++)
{
i.IsNumeric2();
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Restart();
for (var i = 0; i < count; i++)
{
i.IsNumeric3();
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Restart();
for (var i = 0; i < count; i++)
{
i.IsNumeric4();
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
}
Type extension with null-type support.
public static bool IsNumeric(this Type type)
{
if (type == null) { return false; }
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
type = type.GetGenericArguments()[0];
}
switch (Type.GetTypeCode(type))
{
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return true;
default:
return false;
}
}
Try the TypeSupport nuget package for C#. It has support for detecting all numeric types (among many other features):
var extendedType = typeof(int).GetExtendedType();
Assert.IsTrue(extendedType.IsNumericType);
bool IsNumeric(Type type)
=> type == typeof(decimal)
|| type == typeof(int)
|| type == typeof(uint)
|| type == typeof(long)
|| type == typeof(ulong)
|| type == typeof(short)
|| type == typeof(ushort)
|| type == typeof(byte)
|| type == typeof(sbyte)
|| type == typeof(float)
|| type == typeof(double)
;
Just to add to the other answers that were looking at the TypeCode - you can simplify even further if you wish, to avoid the long switch statements:
public static bool IsNumeric(this Type type)
{
var typeCode = (int)Type.GetTypeCode(type);
return typeCode > 4 && typeCode < 16;
}
public static bool IsNumeric(this object source)
{
return source.GetType().IsNumeric();
}
Switch is a little slow, bacause every time methods in the worst situation will be go through all type.
I think, using Dictonary is more nice, in this situation you will be have O(1):
public static class TypeExtensions
{
private static readonly HashSet<Type> NumberTypes = new HashSet<Type>();
static TypeExtensions()
{
NumberTypes.Add(typeof(byte));
NumberTypes.Add(typeof(decimal));
NumberTypes.Add(typeof(double));
NumberTypes.Add(typeof(float));
NumberTypes.Add(typeof(int));
NumberTypes.Add(typeof(long));
NumberTypes.Add(typeof(sbyte));
NumberTypes.Add(typeof(short));
NumberTypes.Add(typeof(uint));
NumberTypes.Add(typeof(ulong));
NumberTypes.Add(typeof(ushort));
}
public static bool IsNumber(this Type type)
{
return NumberTypes.Contains(type);
}
}
EDIT: Well, I modified the code below to be more performant and then ran the tests posted by @Hugo against it. The speeds are about on par with @Hugo's IF using the last item in his sequence (Decimal). If however using the first item 'byte' his takes the cake, but clearly order matters when it comes to performance. Although using the code below is easier to write and more consistent on it's cost, it is not, however, maintainable or future proof-able.
Looks like switching from Type.GetTypeCode() to Convert.GetTypeCode() sped up performance drastically, about 25%, VS Enum.Parse() which was like 10 times slower.
I know this post is old but IF using the TypeCode enum method, easiest (and probably the cheapest) would be something like this:
public static bool IsNumericType(this object o)
{
var t = (byte)Convert.GetTypeCode(o);
return t > 4 && t < 16;
}
Given the following enum definition for TypeCode:
public enum TypeCode
{
Empty = 0,
Object = 1,
DBNull = 2,
Boolean = 3,
Char = 4,
SByte = 5,
Byte = 6,
Int16 = 7,
UInt16 = 8,
Int32 = 9,
UInt32 = 10,
Int64 = 11,
UInt64 = 12,
Single = 13,
Double = 14,
Decimal = 15,
DateTime = 16,
String = 18
}
I haven't tested it thoroughly, but for basic C# numeric types, this would seem to cover it. However, as @JonSkeet mentioned, this enum isn't updated for additional types added to .NET down the road.
.NET 7 (in preview 5 at time of writing) is set to introduce the
INumeric<>
interface which will be implemented by 20 built-in types.
Checking for this interface is likely to be future proof when additional numeric types are added.
static bool IsNumeric(object o){
var numType = typeof(INumber<>);
return o.GetType().GetInterfaces().Any(iface =>
iface.IsGenericType && (iface.GetGenericTypeDefinition() == numType));
}
The types currently implementing this in the .Net 7 preview are:
byte
char
decimal
double
Half
short
int
long
Int128
nint
BigInteger
Complex
NFloat
sbyte
float
ushort
uint
ulong
UInt128
nuint
With the advent of C#11 and Dotnet 7 we have Generic Maths!
You can just check whether or not a type implements the INumber<T> at which point you can do everything you'd expect to be able to on a number.
To do this, you can use typeof(type).isAssignableTo(typeof(INumber<>)).
For example, to retreive all framework provided numeric types, you can run:
List<Type> numericTypes = typeof(Type).Assembly.GetTypes()
.Where(t => t.IsAssignableTo(typeof(INumber<>)))
.ToList();