簡體   English   中英

C# - 如何確定類型是否為數字

[英]C# - how to determine whether a Type is a number

有沒有辦法確定給定的.Net Type 是否是數字? 例如: System.UInt32/UInt16/Double都是數字。 我想避免在Type.FullName上使用較長的 switch-case。

嘗試這個:

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

基本類型是布爾、字節、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

不要使用開關 - 只需使用一組:

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

編輯:與使用類型代碼相比,這樣做的一個優點是,當將新的數字類型引入 .NET(例如BigIntegerComplex )時,它很容易調整 - 而這些類型不會獲得類型代碼。

沒有一個解決方案考慮到 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));
    }

我知道我可以將 nullables 本身添加到我的 HashSet 中。 但是這個解決方案避免了忘記將特定的 Nullable 添加到列表中的危險。

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

刪除優化的注意事項(見 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; }
 
 

基本上是 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

基於Philip 提議的方法,增強了SFun28Nullable類型的內部類型檢查:

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是否為數字類型。

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

具有空類型支持的類型擴展。

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

使用GenericsC# v7.0修改了skeetarviman 的解決方案。

編輯:很多個月后回來以提高代碼質量。

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  

方法 意思是 錯誤 標准差 比率 比率標准差 0代 第一代 第 2 代 已分配
是數字的 16.65 納秒 0.104 納秒 0.087 納秒 1.00 0.00 - - - -
IsNumericAtRuntime 19.26 納秒 0.409 納秒 0.383 納秒 1.16 0.02 0.0038 - - 24乙
IsNullableNumeric 58.65 納秒 0.692 納秒 0.647 納秒 3.53 0.04 0.0038 - - 24乙

HouseofCat/Tesseract Github 回購和 Nuget

嘗試 C# 的TypeSupport nuget 包。 它支持檢測所有數字類型(以及許多其他功能):

var extendedType = typeof(int).GetExtendedType();
Assert.IsTrue(extendedType.IsNumericType);

您可以使用Type.IsPrimitive然后整理出BooleanChar類型,如下所示:

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

編輯:如果您不認為它們是數字的,您可能還想排除IntPtrUIntPtr類型。

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

有沒有一種方法可以確定給定的.Net類型是否為數字? 例如: System.UInt32/UInt16/Double都是數字。 我想避免在Type.FullName上使用長開關。

切換有點慢,因為每次方法在最壞的情況下都會遍歷所有類型。 我認為,使用 Dictonary 更好,在這種情況下,您將擁有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);
    }
}

只是添加到正在查看TypeCode的其他答案 - 如果您願意,您可以進一步簡化,以避免冗長的 switch 語句:

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

這也可能有效。 但是,您可能希望使用 Type.Parse 跟進它,以便之后按照您想要的方式進行轉換。

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

編輯:好吧,我修改了下面的代碼以提高性能,然后針對它運行@Hugo 發布的測試。 使用他的序列中的最后一項(十進制),速度與@Hugo 的 IF 差不多。 但是,如果使用第一個項目“字節”,他將獲得蛋糕,但在性能方面顯然順序很重要。 雖然使用下面的代碼更容易編寫並且在成本上更一致,但是它不是可維護的或未來可證明的。

看起來從 Type.GetTypeCode() 切換到 Convert.GetTypeCode() 大大加快了性能,大約 25%,VS Enum.Parse() 慢了 10 倍。


我知道這篇文章很舊,但如果使用 TypeCode 枚舉方法,最簡單的(可能也是最便宜的)會是這樣的:

public static bool IsNumericType(this object o)
{   
  var t = (byte)Convert.GetTypeCode(o);
  return t > 4 && t < 16;
}

給定 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
}

我還沒有徹底測試它,但對於基本的 C# 數字類型,這似乎涵蓋了它。 但是,正如@JonSkeet 所提到的,此枚舉不會針對將來添加到 .NET 的其他類型進行更新。

.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

隨着 C#11 和 Dotnet 7 的出現,我們有了通用數學!

您可以檢查一個類型是否實現了INumber<T> ,此時您可以對數字執行您希望能夠執行的所有操作。

為此,您可以使用typeof(type).isAssignableTo(typeof(INumber<>))

例如,要檢索所有框架提供的數字類型,您可以運行:

List<Type> numericTypes = typeof(Type).Assembly.GetTypes()
                                      .Where(t => t.IsAssignableTo(typeof(INumber<>)))
                                      .ToList();

基於codybartfast answer using .NET7 and System.Numerics,實際上,這種擴展方法可以解決

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

不幸的是,除了它們都是值類型之外,這些類型沒有太多共同點。 但是為了避免長開關情況,您可以只定義一個包含所有這些類型的只讀列表,然后檢查給定類型是否在列表內。

它們都是值類型(bool 和枚舉除外)。 所以你可以簡單地使用:

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

哎呀! 看錯問題了! 就個人而言,會與Skeet's一起滾動。


人力資源管理, DoSomething你想對你的數據Type做一些事情。 你可以做的是以下

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM