有没有一种方法可以确定给定的.Net类型是否为数字类型?例如:System.UInt32 / UInt16 / Double
都是数字类型。我想避免对Type.FullName
进行长时间的switch-case判断。
尝试这个:
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
var type = o.GetType().GetEnumUnderlyingType() ?? o.GetType();
然后根据类型进行切换:switch (Type.GetTypeCode(type))
- Robert SirreNullable.GetUnderlyingType
而不是 .GetEnumUnderlyingType
。 - Robert Sirre不要使用switch - 只需使用set:
HashSet<Type> NumericTypes = new HashSet<Type>
{
typeof(decimal), typeof(byte), typeof(sbyte),
typeof(short), typeof(ushort), ...
};
编辑:使用这种方法的一个优点是,当新的数字类型引入到 .NET 中时(例如 BigInteger 和 Complex),很容易进行调整,而这些类型将不会获得类型代码。
switch
语句无法用于 Type
,因此你不能这样做。当然你可以在 TypeCode
上使用 switch
,但那是另一回事。 - Jon SkeetHashSet
对于多线程的读取操作是安全的,但你不应该从多个线程进行修改。如果你需要一个适用于各种操作的线程安全的集合,你可以使用ConcurrentDictionary
,或者我猜测有第三方集合可用。 - Jon Skeet这些解决方案都没有考虑到 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));
}
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;
}
return unchecked((uint)Type.GetTypeCode(type) - 5u) <= 10u;
,从而消除了由 &&
引入的分支。 - AnorZaken基本上这是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的提议的方法,利用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
是否是数字。
在C# 7中,与使用TypeCode
和HashSet<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);
}
支持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;
}
}
修改了 skeet 和 arviman 的解决方案,使用了 Generics
和 C# 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 |
.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