简体   繁体   中英

C# - how to determine whether a Type is a number

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 .

Try this:

 
 
 
  
  Type type = object.GetType(); bool isNumber = (type.IsPrimitiveImple && type != typeof(bool) && type != typeof(char));
 
 

The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double,and Single.

Taking Guillaume's solution a little further:

public static bool IsNumericType(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;
  }
}

Usage:

int i = 32;
i.IsNumericType(); // True

string s = "Hello World";
s.IsNumericType(); // False

Don't use a switch - just use a set:

HashSet<Type> NumericTypes = new HashSet<Type>
{
    typeof(decimal), typeof(byte), typeof(sbyte),
    typeof(short), typeof(ushort), ...
};

EDIT: One advantage of this over using a type code is that when new numeric types are introduced into .NET (eg BigInteger and Complex ) it's easy to adjust - whereas those types won't get a type code.

None of the solutions takes Nullable into account.

I modified Jon Skeet's solution a bit:

    private static HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),
        typeof(uint),
        typeof(double),
        typeof(decimal),
        ...
    };

    internal static bool IsNumericType(Type type)
    {
        return NumericTypes.Contains(type) ||
               NumericTypes.Contains(Nullable.GetUnderlyingType(type));
    }

I know I could just add the nullables itself to my HashSet. But this solution avoid the danger of forgetting to add a specific Nullable to your list.

    private static HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),
        typeof(int?),
        ...
    };
public static bool IsNumericType(Type type)
{
  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;
  }
}

Note about optimization removed (see enzi comments) And if you really want to optimize it (losing readability and some safety...):

 
 
 
  
  public static bool IsNumericType(Type type) { TypeCode typeCode = Type.GetTypeCode(type); //The TypeCode of numerical types are between SByte (5) and Decimal (15). return (int)typeCode >= 5 && (int)typeCode <= 15; }
 
 

Basically Skeet's solution but you can reuse it with Nullable types as follows:

public static class TypeHelper
{
    private static readonly HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),  typeof(double),  typeof(decimal),
        typeof(long), typeof(short),   typeof(sbyte),
        typeof(byte), typeof(ulong),   typeof(ushort),  
        typeof(uint), typeof(float)
    };
    
    public static bool IsNumeric(this Type myType)
    {
       return NumericTypes.Contains(Nullable.GetUnderlyingType(myType) ?? myType);
    }
}

Usage Examples:

//static invocation
int someNumber = 5;
TypeHelper.IsNumeric(typeof(someNumber)); //true
string someText = "test";
TypeHelper.IsNumeric(typeof(someText)); //false

//invoke via extension method
typeof(decimal).IsNumeric(); // true
typeof(string).IsNumeric(); // false

Approach based on Philip's proposal , enhanced with SFun28's inner type check for Nullable types:

public static class IsNumericType
{
    public static bool IsNumeric(this Type type)
    {
        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;
            case TypeCode.Object:
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    return Nullable.GetUnderlyingType(type).IsNumeric();
                    //return IsNumeric(Nullable.GetUnderlyingType(type));
                }
                return false;
            default:
                return false;
        }
    }
}

Why this? I had to check if a given Type type is a numeric type, and not if an arbitrary object o is numeric.

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;
        }
    }

Modified skeet's and arviman's solution utilizing Generics , and C# v7.0 .

Edit: Came back many moons later to improve the code quality.

using System;
using System.Collections.Generic;
using System.Numerics;

public static class GenericTypeExtensions
{
    private static readonly HashSet<Type> _numericTypes = new HashSet<Type>
    {
        typeof(int), typeof(double), typeof(decimal),
        typeof(long), typeof(short), typeof(sbyte),
        typeof(byte), typeof(ulong), typeof(ushort),
        typeof(uint), typeof(float), typeof(BigInteger)
    };

    public static bool IsNumeric<T>(this T input)
    {
        if (input is null) return false;

        return _numericTypes.Contains(typeof(T));
    }

    public static bool IsNumericAtRuntime<T>(this T input)
    {
        if (input is null) return false;

        return _numericTypes.Contains(input.GetType());
    }

    /// <summary>
    /// Identifies whether or not this object is a numeric or nullable numeric type.
    /// <para>Examples</para>
    /// <para />int value = 0; true
    /// <para />var objValue = (object)(int)0; true
    /// <para />int? value = 0; true
    /// <para />int? value = null; true
    /// <para />var objValue = (object)(int?)0; true
    /// <para />var objValue = (object)(int?)(null); false - because (int?) is totally lost.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="input"></param>
    /// <returns></returns>
    public static bool IsNullableNumeric<T>(this T input)
    {
        if (input is null)
        {
            return _numericTypes.Contains(Nullable.GetUnderlyingType(typeof(T))); // see what the inner base type is
        }

        return _numericTypes.Contains(input.GetType());
    }

    public static void AddCustomNumericType<T>(this T _) where T : IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
    {
        _numericTypes.Add(typeof(T));
    }

    public static bool TryAddCustomNumeric<T>(T input)
    {
        Type type;
        if (input is null)
        {
            type = Nullable.GetUnderlyingType(typeof(T));
            if (type is null) return false;
        }
        else
        { type = input.GetType(); }

        if (_numericTypes.Contains(type)) return true;

        var interfaces = type.GetInterfaces();
        var count = 0;

        for (var i = 0; i < interfaces.Length; i++)
        {
            switch(interfaces[i])
            {
                case IComparable:
                case IComparable<T>:
                case IConvertible:
                case IEquatable<T>:
                case IFormattable:
                    count++;
                    break;
                default: continue;
            }
        }

        if (count != 5) return false;

        _numericTypes.Add(type);
        return true;
    }

    public static bool TryAddCustomNumericType<T>(Type type)
    {
        if (type is null) return false;

        if (_numericTypes.Contains(type)) return true;

        var interfaces = type.GetInterfaces();
        var count = 0;

        for (var i = 0; i < interfaces.Length; i++)
        {
            switch (interfaces[i])
            {
                case IComparable:
                case IComparable<T>:
                case IConvertible:
                case IEquatable<T>:
                case IFormattable:
                    count++;
                    break;
                default: continue;
            }
        }

        if (count != 5) return false;

        _numericTypes.Add(type);
        return true;
    }

Examples/Unit Tests Pay attention to the Assert.True/False flip.

public class IsNumericTests
{
    [Fact]
    public void IsNumeric()
    {
        var value = 0;

        Assert.True(value.IsNumeric());
    }

    [Fact]
    public void IsObjectNumeric()
    {
        var value = 0;
        var objValue = (object)value;

        Assert.False(objValue.IsNumeric());
    }

    [Fact]
    public void IsNumericAtRuntime()
    {
        var value = 0;

        Assert.True(value.IsNumericAtRuntime());
    }

    [Fact]
    public void IsObjectNumericAtRuntime()
    {
        var value = 0;
        var objValue = (object)value;

        Assert.True(objValue.IsNumericAtRuntime());
    }

    [Fact]
    public void IsNullableNumeric()
    {
        int? value = 0;

        Assert.True(value.IsNullableNumeric());
    }

    [Fact]
    public void IsNullableNumericAsObject()
    {
        int? value = 0;
        var objValue = (object)value;

        Assert.True(objValue.IsNullableNumeric());
    }

    [Fact]
    public void IsNullableNumericWhenNull()
    {
        int? value = null;

        Assert.True(value.IsNullableNumeric());
    }

    [Fact]
    public void IsNullableNumericWhenNullAsObject()
    {
        int? value = null;
        var objValue = (object)value;

        Assert.False(objValue.IsNullableNumeric());
    }

    [Fact]
    public void IsNullableNumericWhenNotNumber()
    {
        string value = "test";

        Assert.False(value.IsNullableNumeric());
    }

    [Fact]
    public void IsNullableNumericWhenNullAndNotNumber()
    {
        Type? value = null;

        Assert.False(value.IsNullableNumeric());
    }
}

Benchmarks/Performance

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using HouseofCat.Extensions;
using System;

[MarkdownExporterAttribute.GitHub]
[MemoryDiagnoser]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net50 | RuntimeMoniker.NetCoreApp31)]
public class IsNumericBenchmark
{
    public int IntValue = 1;
    public long? LongValue = int.MaxValue;

    public object ObjIntValue => (object)IntValue;
    public object ObjLongValue => (object)LongValue;

    [Benchmark(Baseline = true)]
    public void IsNumeric()
    {
        IntValue.IsNumeric();
    }

    [Benchmark]
    public void IsNumericAtRuntime()
    {
        ObjIntValue.IsNumericAtRuntime();
    }

    [Benchmark]
    public void IsNullableNumeric()
    {
        LongValue.IsNullableNumeric();
    }
}

BenchmarkDotNet=v0.13.0, OS=Windows 10.0.18363.1621 (1909/November2019Update/19H2)
Intel Core i7-9850H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK=5.0.400-preview.21277.10
  [Host]   : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT
  .NET 5.0 : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT

Job=.NET 5.0  Runtime=.NET 5.0  

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
IsNumeric 16.65 ns 0.104 ns 0.087 ns 1.00 0.00 - - - -
IsNumericAtRuntime 19.26 ns 0.409 ns 0.383 ns 1.16 0.02 0.0038 - - 24 B
IsNullableNumeric 58.65 ns 0.692 ns 0.647 ns 3.53 0.04 0.0038 - - 24 B

HouseofCat/Tesseract Github Repo & Nuget

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);

You could use Type.IsPrimitive and then sort out the Boolean and Char types, something like this:

bool IsNumeric(Type type)
{
    return type.IsPrimitive && type!=typeof(char) && type!=typeof(bool);
}

EDIT : You may want to exclude the IntPtr and UIntPtr types as well, if you don't consider them to be numeric.

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)
    ;

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 .

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);
    }
}

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();
    }

This may work as well. However, you may want to follow it up with a Type.Parse to cast it the way you want it afterwards.

public bool IsNumeric(object value)
{
    float testValue;
    return float.TryParse(value.ToString(), out testValue);
}

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();

Based on codybartfast answer using .NET7 and System.Numerics, actually, this extension method can be the solution

public static bool IsNumeric(this Type type)
{
    var numType = typeof(INumber<>);
    var result = type.GetInterfaces().Any(i => i.IsGenericType && (i.GetGenericTypeDefinition() == numType));
    return result;
}

Unfortunately these types don't have much in common other than they are all value types. But to avoid a long switch-case you could just define a readonly list with all these types and then just check if the given type is inside the list.

They are all value types (except for bool and maybe enum). So you could simply use:

bool IsNumberic(object o)
{
    return (o is System.ValueType && !(o is System.Boolean) && !(o is System.Enum))
}

oops! Misread the question! Personally, would roll with Skeet's .


hrm, sounds like you want to DoSomething on Type of your data. What you could do is the following

public class MyClass
{
    private readonly Dictionary<Type, Func<SomeResult, object>> _map = 
        new Dictionary<Type, Func<SomeResult, object>> ();

    public MyClass ()
    {
        _map.Add (typeof (int), o => return SomeTypeSafeMethod ((int)(o)));
    }

    public SomeResult DoSomething<T>(T numericValue)
    {
        Type valueType = typeof (T);
        if (!_map.Contains (valueType))
        {
            throw new NotSupportedException (
                string.Format (
                "Does not support Type [{0}].", valueType.Name));
        }
        SomeResult result = _map[valueType] (numericValue);
        return result;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM