.Net判斷一個對象是否爲數值類型探討總結(高養分含量,含最終代碼及跑分)

前一篇發出來後引起了積極的探討,起到了拋磚引玉效果,感謝你們參與。html

吐槽一下:這個問題比其看起來要可貴多得多啊。框架

你們的討論最終仍是沒有一個徹底正確的答案,不過我根據討論結果總結了一個差很少算是最終版的代碼,這裏分享出來,畢竟這是你們共同的智慧結晶,沒有交流和碰撞就沒有這段代碼。性能

 

探討貢獻提名典禮

首先感謝 花生!~~ 以及 NETRUBE 提出了使用 GetTypeCode() 獲取類型代碼的方式,這個比 typeof() 的性能要高,可是有一點侷限性,後面代碼中會指出。測試

image

image

JTANS 以及 入夏 提出的 ValueType 判斷也是有意義的,但顯然僅僅作這個判斷只能肯定是否爲值類型,還不能肯定是否爲咱們要的數值類型。優化

image

image

石山不高 提出 Decimal 是非基元類型,這是正確的,咱們在最終代碼中對其進行了特殊處理。ui

image

花生 (爲何有兩個叫花生的!(+﹏+)~)給出的代碼比較完善,是比較具備總結性的討論成果了,最接近最終版:this

image

其存在的問題主要是 char 和 bool 類型仍是會被當作數值,以及判斷順序須要小幅優化。編碼

 

(可能也許大概差不離就是)最終版代碼(也可能不是)

除了對上述存在問題的改進,還從新調整爲3個方法,分別是用來判斷是否爲數值類型、可空數值類型及可空類型。spa

/// <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版………嗯,咱們將其委託給暴雪工做室開發。)

相關文章
相關標籤/搜索