开发者社区> 老朱教授> 正文

.Net判断一个对象是否为数值类型探讨总结(高营养含量,含最终代码及跑分)

简介:
+关注继续查看

前一篇发出来后引发了积极的探讨,起到了抛砖引玉效果,感谢大家参与。

吐槽一下:这个问题比其看起来要难得多得多啊。

大家的讨论最终还是没有一个完全正确的答案,不过我根据讨论结果总结了一个差不多算是最终版的代码,这里分享出来,毕竟这是大家共同的智慧结晶,没有交流和碰撞就没有这段代码。

 

探讨贡献提名典礼

首先感谢 花生!~~ 以及 NETRUBE 提出了使用 GetTypeCode() 获取类型代码的方式,这个比 typeof() 的性能要高,但是有一点局限性,后面代码中会指出。

image

image

由 JTANS 以及 入夏 提出的 ValueType 判断也是有意义的,但显然仅仅做这个判断只能确定是否为值类型,还不能确定是否为我们要的数值类型。

image

image

由 石山不高 提出 Decimal 是非基元类型,这是正确的,我们在最终代码中对其进行了特殊处理。

image

由 花生 (为什么有两个叫花生的!(+﹏+)~)给出的代码比较完善,是比较具有总结性的讨论成果了,最接近最终版:

image

其存在的问题主要是 char 和 bool 类型还是会被当做数值,以及判断顺序需要小幅优化。

 

(可能也许大概差不离就是)最终版代码(也可能不是)

除了对上述存在问题的改进,还重新调整为3个方法,分别是用来判断是否为数值类型、可空数值类型及可空类型。

/// <summary>
    /// 判断是否为数值类型。
    /// </summary>
    /// <param name="t">要判断的类型</param>
    /// <returns>是否为数值类型</returns>
    public static bool IsNumericType(this Type t)
    {
        var tc = Type.GetTypeCode(t);
        return (t.IsPrimitive && t.IsValueType && !t.IsEnum && tc != TypeCode.Char && tc != TypeCode.Boolean) || tc == TypeCode.Decimal;
    }

    /// <summary>
    /// 判断是否为可空数值类型。
    /// </summary>
    /// <param name="t">要判断的类型</param>
    /// <returns>是否为可空数值类型</returns>
    public static bool IsNumericOrNullableNumericType(this Type t)
    {
        return t.IsNumericType() || (t.IsNullableType() && t.GetGenericArguments()[0].IsNumericType());
    }

    /// <summary>
    /// 判断是否为可空类型。
    /// 注意,直接调用可空对象的.GetType()方法返回的会是其泛型值的实际类型,用其进行此判断肯定返回false。
    /// </summary>
    /// <param name="t">要判断的类型</param>
    /// <returns>是否为可空类型</returns>
    public static bool IsNullableType(this Type t)
    {
        return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
    }

 

为了累死电脑而设计的测试用代码

使用这个测试代码跑可以通过,基本涵盖了常用类型。

[TestClass]
    public class BasicTest
    {
        [TestMethod]
        public void 数值类型判断测试()
        {
            for (int i = 0; i < 500000; i++)
            {
                Assert.IsTrue((591).GetType().IsNumericType());
                Assert.IsTrue((31.131).GetType().IsNumericType());
                Assert.IsTrue((31.131f).GetType().IsNumericType());
                Assert.IsTrue(((Int64)31).GetType().IsNumericType());
                Assert.IsTrue((new decimal(31.351)).GetType().IsNumericType());
                Assert.IsTrue((new Decimal(31.351)).GetType().IsNumericType());
                Assert.IsTrue(((byte)31).GetType().IsNumericType());
                Assert.IsTrue(((UInt64)31).GetType().IsNumericType());
                Assert.IsTrue(((UIntPtr)31).GetType().IsNumericType());
                Assert.IsTrue(((short)31).GetType().IsNumericType());
                Assert.IsTrue(((Single)31).GetType().IsNumericType());

                Assert.IsTrue((typeof(Int64?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(UInt64?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(decimal?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(Decimal?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(UIntPtr?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(byte?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(Single?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(Double?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(float?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(double?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(int?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(short?)).IsNumericOrNullableNumericType());
                Assert.IsTrue((typeof(Nullable<Byte>)).IsNumericOrNullableNumericType());


                Assert.IsFalse(DateTime.Now.GetType().IsNumericType());
                Assert.IsFalse(TimeSpan.FromDays(2).GetType().IsNumericType());
                Assert.IsFalse("aacc".GetType().IsNumericType());
                Assert.IsFalse(System.UriPartial.Path.GetType().IsNumericType());
                Assert.IsFalse('c'.GetType().IsNumericType());
                Assert.IsFalse(false.GetType().IsNumericType());

                Assert.IsFalse((typeof(DateTime?)).IsNumericOrNullableNumericType());
                Assert.IsFalse((typeof(Char?)).IsNumericOrNullableNumericType());
                Assert.IsFalse((typeof(char?)).IsNumericOrNullableNumericType());
                Assert.IsFalse((typeof(System.UriPartial?)).IsNumericOrNullableNumericType());
                Assert.IsFalse((typeof(Boolean?)).IsNumericOrNullableNumericType());
                Assert.IsFalse((typeof(bool?)).IsNumericOrNullableNumericType());
            }

        }
    }

需指出的是:

这里对可空类型判断没有使用 GetType() 方法获取类型对象,因为我测试了一下,可空类型执行 GetType() 返回的仍然是不可空的原类型,直接进行判断是否为数值类型即可。

那么为什么还要做针对可空类型的判断呢?如果你试过在 ASP.Net Mvc 中获取到模型属性的 ModelMetadata 你就会知道,其 ModelType 属性返回的就是 Nullable<> 类型,可空类型的判断就是给这种情况使用的。

 

老外!不服跑个分?

6b1394a6gw1ejy5mn1n6wj20ga095mxk

JEFFERY YOU 提出应该做一个测试,确实数据最有说服力。

我们就以上面的测试代码来跑,注意这是循环五十万轮的测试,每轮执行该方法36次,共计执行一千八百万次,我们让代码连续跑三遍,取第三遍的时间结果(第一遍的包含初始化流程,肯定会慢一些)。

我们的代码测试结果:

image

可以看出这个效率还是蛮高的,平均每轮耗时:0.016546毫秒,平均每次执行方法耗时:0.0004596111111毫秒

然后我们把老外的代码拿过来看一下,它跑不通这个测试,因为以下类型它没做判断:Decimal、Byte、UIntPtr 。

还有个我们测试代码之外的 IntPtr 。

加上这些类型的判断之后,主体方法代码如下:

return t == typeof(int)
         || t == typeof(double)
         || t == typeof(long)
         || t == typeof(short)
         || t == typeof(float)
         || t == typeof(Int16)
         || t == typeof(Int32)
         || t == typeof(Int64)
         || t == typeof(uint)
         || t == typeof(UInt16)
         || t == typeof(UInt32)
         || t == typeof(UInt64)
         || t == typeof(sbyte)
         || t == typeof(Single)
         || t == typeof(Decimal)
         || t == typeof(Byte)
         || t == typeof(UIntPtr)
        || t == typeof(IntPtr);

老外的代码测试结果:

image

这是妥妥的输给我们了,老外给咱跪了,那些支持简单粗暴实打实的朋友错了。

但是稍等一下,老外的代码里其实有些明显的重复判断,比如在C#中 typeof() 获取的 int 和 Int32 其实是一样的,我们来优化一下这些重复:

return t == typeof(Int16)
|| t == typeof(Int32)
|| t == typeof(Int64)
|| t == typeof(Single)
|| t == typeof(Double)
|| t == typeof(UInt16)
|| t == typeof(UInt32)
|| t == typeof(UInt64)
|| t == typeof(Byte)
|| t == typeof(Decimal)
|| t == typeof(SByte)
|| t == typeof(UIntPtr)
|| t == typeof(IntPtr);

优化版的老外代码测试结果:

image

哈,老外还是跪给我们了。

下面我们再将这个代码改进为使用 TypeCode 方式进行判断,这会提高一些性能。

但是需要注意:

从 Enum 类型中获取到的 TypeCode 会是对应 Int32 类型,这不是我们要的结果,需要额外对其进行判断。

TypeCode 枚举中是没有  IntPtr 和 UIntPtr 项的,所以还是要做额外判断。

改进后的代码:

if (t.IsEnum) return false;
        var tc = Type.GetTypeCode(t);
        switch (tc)
        {
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
            case TypeCode.Byte:
            case TypeCode.Decimal:
            case TypeCode.SByte:
                return true;
            default:
                return t == typeof(UIntPtr) || t == typeof(IntPtr);
        }

老外的代码改进为用 TypeCode 方式进行判断后的测试结果:

image

这个效果就很不错了,一千八百万次的量级,仅仅是比我们的最终代码慢了81毫秒(实测三遍时是稳定地输给我们的代码,不是飘出来的偶然浮动结果),这个性能差距可以忽略了。

这也可以看做是另一个最终版的代码了,因为如果根据你的使用环境来把常用类型放到最前面的话,性能还会更好(尽管你根本感觉不到单次万分之几毫秒的差别),但是不可回避的是对那些我们没有预见到的类型的支持问题,比如这  IntPtr 和 UIntPtr ,在我们前面给出的最终版代码中这两个类型是未做特殊适配就天然支持的。

所以如果你重视优雅度、扩展性和编码知识层级的话,还是建议你使用我前面给出的最终代码。

 

巡回总结报告会演讲

看似非常简单的问题,背后却有这么深的水啊,若没有大家的讨论,断然不会得到这样的成果,并且学到这么多知识。

没有完美的代码,我们期待更好,在此继续讨论吧,也许交流碰撞后还会有更优秀的方案!

(微软:卧槽,看你们这么苦逼,我给你们直接做一个属性出来吧,请期待.Net 框架v10.29博主生日特别无码汉化激情未删减导演剪辑泄露蓝光3D版………嗯,我们将其委托给暴雪工作室开发。)


本文转自斯克迪亚博客园博客,原文链接:http://www.cnblogs.com/SkyD/p/4058486.html,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【.Net底层剖析】2.stfld指令-给对象的字段赋值
【.Net底层剖析】2.stfld指令-给对象的字段赋值
57 0
.NET对象清理
.NET对象清理
53 0
如何优雅地使用对象云存储-.NET篇
​## 如何优雅地使用腾讯云COS-.NET篇 代码下载地址 https://github.com/whuanle/txypx20190809 前提 创建子账号 打开 https://console.cloud.tencent.com/cam 创建子用户,设置子账号策略为 AdministratorAccess ,或者参考https://cloud.tencent.com/document/product/436/11714 ,添加访问 COS 的权限 记录子用户的 账号ID。
954 0
.NET读取json数据并绑定到对象
原文:.NET读取json数据并绑定到对象 需要引用的命名空间:       读取的具体应用: this代表本实体(对象),通过PopulateObject,直接将读取到的json数据与对象进行绑定       Json保存的具体应用: 将对象保存为Json     JOb...
765 0
从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十五 ║ Vue前篇:JS对象&字面量&this
缘起 书接上文《[从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十四 ║ VUE 计划书 & 我的前后端开发简史](https://www.cnblogs.com/laozhang-is-phi/p/9577805.html)》,昨天咱们说到了以我的经历说明的web开发经历的几个阶段,而且也说到了Vue系列需要讲到的知识点,今天就正式开始Code,当然今天的代码都特别简单,希望大家慢慢的学习,今天主要讲的是JS高级——关于面向对象的语法。
1493 0
从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十三 || DTOs 对象映射使用,项目部署Windows+Linux完整版
代码已上传Github+Gitee,文末有地址 番外:时间真快,今天终于到了系统打包的日子,虽然项目还是有很多问题,虽然后边还有很多的内容要说要学,但是想着初级基本的.Net Core 用到的基本至少就这么多了(接口文档,项目框架,持久化ORM,依赖注入,AOP,分布式缓存,CORS跨域等等...
1728 0
从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十二 || 三种跨域方式比较,DTOs(数据传输对象)初探
更新反馈 1、博友@落幕残情童鞋说到了,Nginx反向代理实现跨域,因为我目前还没有使用到,给忽略了,这次记录下,为下次补充。 代码已上传Github+Gitee,文末有地址   今天忙着给小伙伴们提出的问题解答,时间上没把握好,都快下班了,赶紧发布:书说上文《从壹开始前后端分离【 .
1493 0
.Net Web Api返回Json数据中原对象变量名大小写问题
这两天在工作中使用SignalR的WebSocket做数据实时传递的功能开发,在后端主动向前端广播数据以Json传递时,前端获取的Json中对应类的变量名首字母默认传递的是大写。而前端一直获取到的后台返回给Json中字段均为首字母小写的驼峰命名法。
2075 0
.NET[C#]中实现实体对象深拷贝(克隆/复制)的几种方法
.NET[C#]中实现实体对象深拷贝(克隆/复制)的几种方法,总有一种适合你。 方式一 使用二进制流 using System; using System.IO; using System.Runtime.
3426 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
数据+算法定义新世界
立即下载
中美教育差异之观察
立即下载
Gululu重新定义儿童饮水习惯
立即下载