C# - 如何确定一个类型是否为数字

144

有没有一种方法可以确定给定的.Net类型是否为数字类型?例如:System.UInt32 / UInt16 / Double都是数字类型。我想避免对Type.FullName进行长时间的switch-case判断。


2
重复的问题,与其他一些问题非常相似。 - H H
22个回答

148

尝试这个:

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

原始数据类型包括Boolean、Byte、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、Char、Double和Single。

Guillaume的解决方案推进一步:

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

使用方法:

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

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

3
这需要重新设计以适应.NET 4.0中没有类型代码的新数字类型。 - Jon Skeet
18
你怎么可以根据当前技术对我的答案进行投票?也许在 .NET 62 中,int 类型将被移除 - 那你是不是会对所有使用 int 的答案进行投票? - Philip Wallace
@JonSkeet,对于那些新的数字类型,GetTypeCode返回什么?文档表明返回null,但我不太清楚因为我不知道这些新的数字类型是什么。 - kdbanman
2
如果您想使用此方法检查可空类型是否为“数字”,则可以使用以下代码: var type = o.GetType().GetEnumUnderlyingType() ?? o.GetType(); 然后根据类型进行切换:switch (Type.GetTypeCode(type)) - Robert Sirre
1
更正:请使用 Nullable.GetUnderlyingType 而不是 .GetEnumUnderlyingType - Robert Sirre
显示剩余5条评论

112

不要使用switch - 只需使用set:

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

编辑:使用这种方法的一个优点是,当新的数字类型引入到 .NET 中时(例如 BigIntegerComplex),很容易进行调整,而这些类型将不会获得类型代码。


4
你如何使用HashSet? - RvdK
9
NumericTypes.Contains(whatever)? - mqp
5
bool isANumber = NumericTypes.Contains(classInstance.GetType());这行代码的意思是:判断classInstance对象所属的类型是否为数字类型,如果是,则将isANumber赋值为true,否则为false。其中NumericTypes是一个包含数字类型的集合,classInstance.GetType()返回的是classInstance对象的实际类型。 - Yuriy Faktorovich
6
@RolfKristensen:switch 语句无法用于 Type,因此你不能这样做。当然你可以在 TypeCode 上使用 switch,但那是另一回事。 - Jon Skeet
1
@JeffFischer:我认为一旦构建完成,HashSet对于多线程的读取操作是安全的,但你不应该从多个线程进行修改。如果你需要一个适用于各种操作的线程安全的集合,你可以使用ConcurrentDictionary,或者我猜测有第三方集合可用。 - Jon Skeet
显示剩余4条评论

83

这些解决方案都没有考虑到 Nullable。

我稍微修改了Jon Skeet的解决方案:

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

我知道我可以将可空类型本身添加到HashSet中。 但是这种解决方案避免了忘记添加特定Nullable到列表的风险。
    private static HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),
        typeof(int?),
        ...
    };

2
可空类型真的是数值类型吗?据我所知,null不是一个数字。 - IS4
3
这取决于你想要实现什么目的。在我的情况下,我需要包含可为空的内容。但我也可以想象出某些情况下这不是期望的行为。 - Jürgen Steinblock
很好!将可空数字视为数字在UI输入验证中非常有用。 - guogangj
1
@IllidanS4 检查的是 类型 而不是值。在大多数情况下,可空数值类型应被视为数值类型。当然,如果检查的是值且该值为空,则不应将其视为数值类型。 - nawfal

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

注意: 已移除有关优化的内容(请参见 enzi 评论) 如果你真的想要进行优化(会损失可读性和一些安全性……):

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


15
我知道这个答案很旧,但是我最近遇到了这样一个开关:不要使用建议的优化!我查看了从这样一个开关生成的IL代码,并注意到编译器已经应用了优化(在IL中,从类型代码中减去5,然后将0到10之间的值视为真)。因此,这个开关应该被用于它更可读、更安全,而且同样快速。 - enzi
2
如果您真的想要进行优化并且不关心可读性,最佳代码将是 return unchecked((uint)Type.GetTypeCode(type) - 5u) <= 10u;,从而消除了由 && 引入的分支。 - AnorZaken

24

基本上这是Skeet的解决方案,但你可以按照以下方式重复使用它来处理Nullable类型:

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

使用示例:

//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

11

基于Philip的提议的方法,利用SFun28的内部类型检查增强了对Nullable类型的支持:

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

为什么要这样做?我需要检查给定的 Type type 是否是数字类型,而不是任意的 object o 是否是数字。


6

在C# 7中,与使用TypeCodeHashSet<Type>的switch case相比,这种方法可以提供更好的性能:

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;

以下是测试内容:

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

6

支持null类型的类型扩展。

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

5

修改了 skeetarviman 的解决方案,使用了 GenericsC# v7.0

编辑:很久以后回来提高代码质量。

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

示例/单元测试 注意Assert.True / False翻转。

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

基准测试/性能

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  

方法 平均值 误差 标准差 比率 比率标准偏差 Gen 0 Gen 1 Gen 2 已分配内存
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


4

.NET 7(在撰写时为预览版5)将引入INumeric<>接口,该接口将由20种内置类型实现。

检查此接口可能是未来的证明,在添加其他数字类型时可以使用。

static bool IsNumeric(object o){
    var numType = typeof(INumber<>);
    return o.GetType().GetInterfaces().Any(iface =>
        iface.IsGenericType && (iface.GetGenericTypeDefinition() == numType));
}

目前在 .Net 7 预览版中实现此功能的类型有:

byte
char
decimal
double
Half
short
int
long
Int128
nint
BigInteger
Complex
NFloat
sbyte
float
ushort
uint
ulong
UInt128
nuint

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接