繁体   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