簡體   English   中英

如何檢查對象是否可為空?

[英]How to check if an object is nullable?

如何檢查給定對象是否可為空,換句話說,如何實現以下方法...

bool IsNullableValueType(object o)
{
    ...
}

我正在尋找可為空的值類型。 我沒有考慮引用類型。

//Note: This is just a sample. The code has been simplified 
//to fit in a post.

public class BoolContainer
{
    bool? myBool = true;
}

var bc = new BoolContainer();

const BindingFlags bindingFlags = BindingFlags.Public
                        | BindingFlags.NonPublic
                        | BindingFlags.Instance
                        ;


object obj;
object o = (object)bc;

foreach (var fieldInfo in o.GetType().GetFields(bindingFlags))
{
    obj = (object)fieldInfo.GetValue(o);
}

obj現在指的是值為true bool ( System.Boolean ) 類型的對象。 我真正想要的是Nullable<bool>類型的對象

所以現在作為一種解決方法,我決定檢查 o 是否可以為空並圍繞 obj 創建一個可為空的包裝器。

有兩種可空類型 - Nullable<T>和引用類型。

Jon 糾正了我,如果裝箱很難獲得類型,但你可以使用泛型: - 那么下面怎么樣。 這實際上是在測試類型T ,但是將obj參數純粹用於泛型類型推斷(以便於調用)——盡管沒有obj參數,它的工作方式幾乎相同。

static bool IsNullable<T>(T obj)
{
    if (obj == null) return true; // obvious
    Type type = typeof(T);
    if (!type.IsValueType) return true; // ref-type
    if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable<T>
    return false; // value-type
}

但是,如果您已經將值裝箱到對象變量中,這將不會奏效。

微軟文檔: https : //docs.microsoft.com/en-us/dotnet/csharp/programming-guide/nullable-types/how-to-identify-a-nullable-type

使用方法重載有一個非常簡單的解決方案

http://deanchalk.com/is-it-nullable/

摘錄:

public static class ValueTypeHelper
{
    public static bool IsNullable<T>(T t) { return false; }
    public static bool IsNullable<T>(T? t) where T : struct { return true; }
}

那么

static void Main(string[] args)
{
    int a = 123;
    int? b = null;
    object c = new object();
    object d = null;
    int? e = 456;
    var f = (int?)789;
    bool result1 = ValueTypeHelper.IsNullable(a); // false
    bool result2 = ValueTypeHelper.IsNullable(b); // true
    bool result3 = ValueTypeHelper.IsNullable(c); // false
    bool result4 = ValueTypeHelper.IsNullable(d); // false
    bool result5 = ValueTypeHelper.IsNullable(e); // true
    bool result6 = ValueTypeHelper.IsNullable(f); // true

這對我有用,看起來很簡單:

static bool IsNullable<T>(T obj)
{
    return default(T) == null;
}

對於值類型:

static bool IsNullableValueType<T>(T obj)
{
    return default(T) == null && typeof(T).BaseType != null && "ValueType".Equals(typeof(T).BaseType.Name);
}

“如何檢查類型是否可為空?”的問題實際上是“如何檢查一個類型是否為Nullable<> ?”,可以概括為“如何檢查一個類型是否是某個泛型類型的構造類型?”,這樣它不僅可以回答“Is Nullable<int> ”這個問題Nullable<int>一個Nullable<> ?”,還有“ List<int>是一個List<>嗎?”。

大多數提供的解決方案使用Nullable.GetUnderlyingType()方法,這顯然只適用於Nullable<>的情況。 我沒有看到適用於任何泛型類型的通用反射解決方案,所以我決定將它添加到這里以供后代使用,即使這個問題很久以前就已經得到了回答。

要使用反射檢查類型是否為Nullable<>某種形式,您首先必須將構造的泛型類型(例如Nullable<int> )轉換為泛型類型定義Nullable<> 您可以通過使用Type類的GetGenericTypeDefinition()方法來做到這一點。 然后,您可以將結果類型與Nullable<>進行比較:

Type typeToTest = typeof(Nullable<int>);
bool isNullable = typeToTest.GetGenericTypeDefinition() == typeof(Nullable<>);
// isNullable == true

這同樣適用於任何泛型類型:

Type typeToTest = typeof(List<int>);
bool isList = typeToTest.GetGenericTypeDefinition() == typeof(List<>);
// isList == true

幾種類型可能看起來相同,但不同數量的類型參數意味着它是一種完全不同的類型。

Type typeToTest = typeof(Action<DateTime, float>);
bool isAction1 = typeToTest.GetGenericTypeDefinition() == typeof(Action<>);
bool isAction2 = typeToTest.GetGenericTypeDefinition() == typeof(Action<,>);
bool isAction3 = typeToTest.GetGenericTypeDefinition() == typeof(Action<,,>);
// isAction1 == false
// isAction2 == true
// isAction3 == false

由於Type對象每個類型都實例化一次,因此您可以檢查它們之間的引用相等性。 因此,如果要檢查兩個對象是否具有相同的泛型類型定義,可以編寫:

var listOfInts = new List<int>();
var listOfStrings = new List<string>();

bool areSameGenericType =
    listOfInts.GetType().GetGenericTypeDefinition() ==
    listOfStrings.GetType().GetGenericTypeDefinition();
// areSameGenericType == true

如果您想檢查一個對象是否可以為空,而不是一個Type ,那么您可以將上述技術與 Marc Gravell 的解決方案一起使用來創建一個相當簡單的方法:

static bool IsNullable<T>(T obj)
{
    if (!typeof(T).IsGenericType)
        return false;

    return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
}

那么,你可以使用:

return !(o is ValueType);

...但對象本身不可為空或以其他方式 -類型是。 你打算怎么用這個?

我能想到的最簡單的方法是:

public bool IsNullable(object obj)
{
    Type t = obj.GetType();
    return t.IsGenericType 
        && t.GetGenericTypeDefinition() == typeof(Nullable<>);
}

我想出的最簡單的解決方案是將 Microsoft 的解決方案( How to: Identification a Nullable Type (C# Programming Guide) )作為擴展方法實現:

public static bool IsNullable(this Type type)
{
    return Nullable.GetUnderlyingType(type) != null;
}

然后可以這樣調用:

bool isNullable = typeof(int).IsNullable();

這似乎也是訪問IsNullable()一種合乎邏輯的方式,因為它適用於Type類的所有其他IsXxxx()方法。

這里有兩個問題:1)測試一個類型是否可以為空; 和 2) 測試以查看對象是否表示可為空的類型。

對於問題 1(測試類型),這是我在自己的系統中使用的解決方案: TypeIsNullable-check 解決方案

對於問題 2(測試對象),上面的 Dean Chalk 的解決方案適用於值類型,但不適用於引用類型,因為使用 <T> 重載總是返回 false。 由於引用類型本質上是可為空的,因此測試引用類型應始終返回 true。 有關這些語義的解釋,請參閱下面的注釋 [關於“可空性”]。 因此,這是我對 Dean 方法的修改:

    public static bool IsObjectNullable<T>(T obj)
    {
        // If the parameter-Type is a reference type, or if the parameter is null, then the object is always nullable
        if (!typeof(T).IsValueType || obj == null)
            return true;

        // Since the object passed is a ValueType, and it is not null, it cannot be a nullable object
        return false; 
    }

    public static bool IsObjectNullable<T>(T? obj) where T : struct
    {
        // Always return true, since the object-type passed is guaranteed by the compiler to always be nullable
        return true;
    }

這是我對上述解決方案的客戶端測試代碼的修改:

    int a = 123;
    int? b = null;
    object c = new object();
    object d = null;
    int? e = 456;
    var f = (int?)789;
    string g = "something";

    bool isnullable = IsObjectNullable(a); // false 
    isnullable = IsObjectNullable(b); // true 
    isnullable = IsObjectNullable(c); // true 
    isnullable = IsObjectNullable(d); // true 
    isnullable = IsObjectNullable(e); // true 
    isnullable = IsObjectNullable(f); // true 
    isnullable = IsObjectNullable(g); // true

我在 IsObjectNullable<T>(T t) 中修改 Dean 的方法的原因是他的原始方法對於引用類型總是返回 false。 由於像 IsObjectNullable 這樣的方法應該能夠處理引用類型的值,並且由於所有引用類型本質上都是可為空的,因此如果傳遞了引用類型或 null,則該方法應始終返回 true。

以上兩種方法可以替換為以下單一方法並實現相同的輸出:

    public static bool IsObjectNullable<T>(T obj)
    {
        Type argType = typeof(T);
        if (!argType.IsValueType || obj == null)
            return true;
        return argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(Nullable<>);
    }

然而,最后一種單一方法的問題在於,當使用 Nullable<T> 參數時,性能會受到影響。 與在 IsObjectNullable 調用中使用 Nullable<T> 類型參數時允許編譯器選擇前面顯示的第二個方法重載相比,執行此單個方法的最后一行所需的處理器時間要多得多。 因此,最佳解決方案是使用此處說明的兩種方法。

警告:只有在使用原始對象引用或精確副本調用時,此方法才能可靠地工作,如示例中所示。 但是,如果可空對象被裝箱到另一個類型(例如對象等)而不是保持其原始 Nullable<> 形式,則此方法將無法可靠地工作。 如果調用此方法的代碼未使用原始的、未裝箱的對象引用或精確副本,則無法使用此方法可靠地確定對象的可空性。

在大多數編碼場景中,為了確定可空性,必須依賴於測試原始對象的類型,而不是其引用(例如,代碼必須能夠訪問對象的原始類型才能確定可空性)。 在這些更常見的情況下,IsTypeNullable(請參閱鏈接)是確定可空性的可靠方法。

PS - 關於“可空性”

我應該重復我在另一篇文章中所做的關於可空性的聲明,它直接適用於正確解決這個主題。 也就是說,我認為這里討論的重點不應該是如何檢查一個對象是否是通用的 Nullable 類型,而是是否可以為它的類型的對象分配一個 null 值。 換句話說,我認為我們應該確定一個對象類型是否可以為 null,而不是它是否為 Nullable。 區別在於語義,即確定可空性的實際原因,這通常是最重要的。

在使用類型可能在運行時之前未知的對象(Web 服務、遠程調用、數據庫、提要等)的系統中,一個常見的要求是確定是否可以將空值分配給對象,或者對象是否可能包含一個空值。 對不可空類型執行此類操作可能會產生錯誤,通常是異常,這在性能和編碼要求方面都非常昂貴。 為了采取主動避免此類問題的首選方法,需要確定任意類型的對象是否能夠包含空值; 即,它是否通常是“可空的”。

在非常實際和典型的意義上,.NET 術語中的可空性並不一定意味着對象的類型是 Nullable 的一種形式。 實際上很多情況下,對象都有引用類型,可以包含空值,因此都是可以為空的; 這些都沒有 Nullable 類型。 因此,在大多數情況下,出於實際目的,應該針對可空性的一般概念進行測試,而不是 Nullable 的實現相關概念。 因此,我們不應該僅僅關注 .NET Nullable 類型,而應該在關注可空性的一般實用概念的過程中結合我們對其要求和行為的理解。

小心,當裝箱一個可為空類型( Nullable<int>或 int? 例如):

int? nullValue = null;
object boxedNullValue = (object)nullValue;
Debug.Assert(boxedNullValue == null);

int? value = 10;
object boxedValue = (object)value;
Debug.Assert( boxedValue.GetType() == typeof(int))

它變成了一個真正的引用類型,所以你失去了它可以為空的事實。

也許有點離題,但仍然有一些有趣的信息。 我發現很多人使用Nullable.GetUnderlyingType() != null來標識類型是否可為空。 這顯然有效,但 Microsoft 建議使用以下type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) (請參閱http://msdn.microsoft.com/en-us/library/ms366789.aspx )。

我是從性能的角度來看的。 下面的測試(一百萬次嘗試)的結論是,當類型為可空類型時,Microsoft 選項提供最佳性能。

Nullable.GetUnderlyingType(): 1335 毫秒(慢 3 倍)

GetGenericTypeDefinition() == typeof(Nullable<>): 500ms

我知道我們談論的是少量時間,但每個人都喜歡調整毫秒 :-)! 因此,如果您的老板希望您減少一些毫秒,那么這就是您的救星...

/// <summary>Method for testing the performance of several options to determine if a type is     nullable</summary>
[TestMethod]
public void IdentityNullablePerformanceTest()
{
    int attempts = 1000000;

    Type nullableType = typeof(Nullable<int>);

    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    for (int attemptIndex = 0; attemptIndex < attempts; attemptIndex++)
    {
        Assert.IsTrue(Nullable.GetUnderlyingType(nullableType) != null, "Expected to be a nullable"); 
    }

    Console.WriteLine("Nullable.GetUnderlyingType(): {0} ms", stopwatch.ElapsedMilliseconds);

    stopwatch.Restart();

    for (int attemptIndex = 0; attemptIndex < attempts; attemptIndex++)
   {
       Assert.IsTrue(nullableType.IsGenericType && nullableType.GetGenericTypeDefinition() == typeof(Nullable<>), "Expected to be a nullable");
   }

   Console.WriteLine("GetGenericTypeDefinition() == typeof(Nullable<>): {0} ms", stopwatch.ElapsedMilliseconds);
   stopwatch.Stop();
}

我認為使用 Microsoft 建議的針對IsGenericType的測試是好的,但是在GetUnderlyingType的代碼中,Microsoft 使用了額外的測試來確保您沒有傳入泛型類型定義Nullable<>

 public static bool IsNullableType(this Type nullableType) =>
    // instantiated generic type only                
    nullableType.IsGenericType &&
    !nullableType.IsGenericTypeDefinition &&
    Object.ReferenceEquals(nullableType.GetGenericTypeDefinition(), typeof(Nullable<>));

這個版本:

  • 緩存結果更快,
  • 不需要不必要的變量,如 Method(T obj)
  • 不復雜:),
  • 只是靜態泛型類,有一次計算字段

public static class IsNullable<T>
{
    private static readonly Type type = typeof(T);
    private static readonly bool is_nullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
    public static bool Result { get { return is_nullable; } }
}

bool is_nullable = IsNullable<int?>.Result;

這是我想出的,因為其他一切似乎都失敗了 - 至少在PLC 上 - Portable Class Library / .NET Core with >= C# 6

解決方案:為任何類型TNullable<T>擴展靜態方法,並使用與底層類型匹配的靜態擴展方法將被調用並優先於泛型T擴展方法的事實。

對於T

public static partial class ObjectExtension
{
    public static bool IsNullable<T>(this T self)
    {
        return false;
    }
}

對於Nullable<T>

public static partial class NullableExtension
{
    public static bool IsNullable<T>(this Nullable<T> self) where T : struct
    {
        return true;
    }
}

使用反射和type.IsGenericType ... 在我當前的 .NET 運行時集上不起作用。 MSDN 文檔也沒有幫助。

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {…}

部分原因是反射 API 在 .NET Core 中發生了相當大的變化。

一個簡單的方法來做到這一點:

    public static bool IsNullable(this Type type)
    {
        if (type.IsValueType) return Activator.CreateInstance(type) == null;

        return true;
    }

這些是我的單元測試並且都通過了

    IsNullable_String_ShouldReturn_True
    IsNullable_Boolean_ShouldReturn_False
    IsNullable_Enum_ShouldReturn_Fasle
    IsNullable_Nullable_ShouldReturn_True
    IsNullable_Class_ShouldReturn_True
    IsNullable_Decimal_ShouldReturn_False
    IsNullable_Byte_ShouldReturn_False
    IsNullable_KeyValuePair_ShouldReturn_False

實際單元測試

    [TestMethod]
    public void IsNullable_String_ShouldReturn_True()
    {
        var typ = typeof(string);
        var result = typ.IsNullable();
        Assert.IsTrue(result);
    }

    [TestMethod]
    public void IsNullable_Boolean_ShouldReturn_False()
    {
        var typ = typeof(bool);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }

    [TestMethod]
    public void IsNullable_Enum_ShouldReturn_Fasle()
    {
        var typ = typeof(System.GenericUriParserOptions);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }

    [TestMethod]
    public void IsNullable_Nullable_ShouldReturn_True()
    {
        var typ = typeof(Nullable<bool>);
        var result = typ.IsNullable();
        Assert.IsTrue(result);
    }

    [TestMethod]
    public void IsNullable_Class_ShouldReturn_True()
    {
        var typ = typeof(TestPerson);
        var result = typ.IsNullable();
        Assert.IsTrue(result);
    }

    [TestMethod]
    public void IsNullable_Decimal_ShouldReturn_False()
    {
        var typ = typeof(decimal);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }

    [TestMethod]
    public void IsNullable_Byte_ShouldReturn_False()
    {
        var typ = typeof(byte);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }

    [TestMethod]
    public void IsNullable_KeyValuePair_ShouldReturn_False()
    {
        var typ = typeof(KeyValuePair<string, string>);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }

暫無
暫無

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

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