如何确定运行时对象是否为可空值类型
How to determine if a runtime object is of a nullable value type
首先:这不是 How to check if an object is nullable? 的副本。或者,至少,这个问题没有提供有用的答案,作者的进一步阐述实际上询问了如何确定给定的 type(例如 returned from MethodInfo.ReturnType) 可以为空。
不过,这很容易。困难的是确定一个在编译时类型未知的运行时对象是否为可空类型。考虑:
public void Foo(object obj)
{
var bar = IsNullable(obj);
}
private bool IsNullable(object obj)
{
var type = obj.GetType();
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
// Alternatively something like:
// return !(Nullable.GetUnderlyingType(type) != null);
}
这 没有 按预期工作,因为 GetType()
调用会导致装箱操作 (https://msdn.microsoft.com/en-us/library/ms366789.aspx),并且会 return基础值类型,而不是可空类型。因此 IsNullable()
将始终 return false。
现在,以下技巧使用类型参数推断来获取(未装箱的)类型:
private bool IsNullable<T>(T obj)
{
var type = typeof(T);
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
这最初看起来很有希望。但是,类型参数推断仅在对象的类型在编译时已知时才有效。所以:
public void Foo(object obj)
{
int? genericObj = 23;
var bar1 = IsNullable(genericObj); // Works
var bar2 = IsNullable(obj); // Doesn't work (type parameter is Object)
}
总而言之:一般问题不是确定类型是否可为空,而是首先确定类型。
所以,我的挑战是:如何确定运行时对象(上例中的 obj 参数)是否可为空?把我吹走:)
好吧,你来晚了。装箱值不再具有您要查找的类型信息 - 装箱 int?
值会导致 null
或装箱 int
。在某种程度上,它类似于在 null
上调用 GetType
- 它没有任何意义,没有类型信息。
如果可以,尽可能坚持使用泛型方法而不是装箱(dynamic
对于实际可空值和 object
之间的某些接口非常有帮助)。如果你不能,你将不得不使用你自己的 "boxing" - 或者甚至只是创建你自己的 Nullable
-like 类型,那将是一个 class
而不是一个非常 hacky struct
-类似的东西 :D
如果需要,事实上,您甚至可以让 Nullable
键入 struct
。破坏类型信息的不是 struct
-ness——破坏该信息的不是装箱本身,而是 CLR hack 使 Nullable
能够匹配不可空值的性能。它非常聪明而且非常有用,但它打破了一些基于反射的 hack(就像你正在尝试做的那样)。
这按预期工作:
struct MyNullable<T>
{
private bool hasValue;
private T value;
public static MyNullable<T> FromValue(T value)
{
return new MyNullable<T>() { hasValue = true, value = value };
}
public static implicit operator T (MyNullable<T> n)
{
return n.value;
}
}
private bool IsMyNullable(object obj)
{
if (obj == null) return true; // Duh
var type = obj.GetType().Dump();
return type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(MyNullable<>);
}
用 System.Nullable
做同样的事情不会;即使只做 new int?(42).GetType()
也会给你 System.Int32
而不是 System.Nullable<System.Int32>
.
System.Nullable
不是真正的类型 - 它在运行时得到特殊处理。它所做的事情您根本无法用自己的类型复制,因为 hack 甚至不在 IL 的类型定义中——它就在 CLR 本身中。另一个不错的抽象泄漏是可空类型不被视为 struct
- 如果您的泛型类型约束是 struct
,则不能使用可空类型。为什么?好吧,添加此约束意味着使用例如new T?()
- 否则这是不可能的,因为您不能使可空的可空。使用我在这里写的 MyNullable
类型很容易,但使用 System.Nullable
.
就不行
编辑:
CLI 规范的相关部分(1.8.2.4 - 装箱和拆箱值):
All value types have an operation called box. Boxing a value of any
value type produces its boxed value; i.e., a value of the
corresponding boxed type containing a bitwise copy of the original
value. If the value type is a nullable type—defined as an
instantiation of the value type System.Nullable—the result is a
null reference or bitwise copy of its Value property of type T,
depending on its HasValue property (false and true, respectively). All
boxed types have an operation called unbox, which results in a managed
pointer to the bit representation of the value.
因此,根据定义,对可空类型的 box
操作会产生空引用或存储的值,而不是 "boxed nullable"。
首先:这不是 How to check if an object is nullable? 的副本。或者,至少,这个问题没有提供有用的答案,作者的进一步阐述实际上询问了如何确定给定的 type(例如 returned from MethodInfo.ReturnType) 可以为空。
不过,这很容易。困难的是确定一个在编译时类型未知的运行时对象是否为可空类型。考虑:
public void Foo(object obj)
{
var bar = IsNullable(obj);
}
private bool IsNullable(object obj)
{
var type = obj.GetType();
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
// Alternatively something like:
// return !(Nullable.GetUnderlyingType(type) != null);
}
这 没有 按预期工作,因为 GetType()
调用会导致装箱操作 (https://msdn.microsoft.com/en-us/library/ms366789.aspx),并且会 return基础值类型,而不是可空类型。因此 IsNullable()
将始终 return false。
现在,以下技巧使用类型参数推断来获取(未装箱的)类型:
private bool IsNullable<T>(T obj)
{
var type = typeof(T);
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
这最初看起来很有希望。但是,类型参数推断仅在对象的类型在编译时已知时才有效。所以:
public void Foo(object obj)
{
int? genericObj = 23;
var bar1 = IsNullable(genericObj); // Works
var bar2 = IsNullable(obj); // Doesn't work (type parameter is Object)
}
总而言之:一般问题不是确定类型是否可为空,而是首先确定类型。
所以,我的挑战是:如何确定运行时对象(上例中的 obj 参数)是否可为空?把我吹走:)
好吧,你来晚了。装箱值不再具有您要查找的类型信息 - 装箱 int?
值会导致 null
或装箱 int
。在某种程度上,它类似于在 null
上调用 GetType
- 它没有任何意义,没有类型信息。
如果可以,尽可能坚持使用泛型方法而不是装箱(dynamic
对于实际可空值和 object
之间的某些接口非常有帮助)。如果你不能,你将不得不使用你自己的 "boxing" - 或者甚至只是创建你自己的 Nullable
-like 类型,那将是一个 class
而不是一个非常 hacky struct
-类似的东西 :D
如果需要,事实上,您甚至可以让 Nullable
键入 struct
。破坏类型信息的不是 struct
-ness——破坏该信息的不是装箱本身,而是 CLR hack 使 Nullable
能够匹配不可空值的性能。它非常聪明而且非常有用,但它打破了一些基于反射的 hack(就像你正在尝试做的那样)。
这按预期工作:
struct MyNullable<T>
{
private bool hasValue;
private T value;
public static MyNullable<T> FromValue(T value)
{
return new MyNullable<T>() { hasValue = true, value = value };
}
public static implicit operator T (MyNullable<T> n)
{
return n.value;
}
}
private bool IsMyNullable(object obj)
{
if (obj == null) return true; // Duh
var type = obj.GetType().Dump();
return type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(MyNullable<>);
}
用 System.Nullable
做同样的事情不会;即使只做 new int?(42).GetType()
也会给你 System.Int32
而不是 System.Nullable<System.Int32>
.
System.Nullable
不是真正的类型 - 它在运行时得到特殊处理。它所做的事情您根本无法用自己的类型复制,因为 hack 甚至不在 IL 的类型定义中——它就在 CLR 本身中。另一个不错的抽象泄漏是可空类型不被视为 struct
- 如果您的泛型类型约束是 struct
,则不能使用可空类型。为什么?好吧,添加此约束意味着使用例如new T?()
- 否则这是不可能的,因为您不能使可空的可空。使用我在这里写的 MyNullable
类型很容易,但使用 System.Nullable
.
编辑:
CLI 规范的相关部分(1.8.2.4 - 装箱和拆箱值):
All value types have an operation called box. Boxing a value of any value type produces its boxed value; i.e., a value of the corresponding boxed type containing a bitwise copy of the original value. If the value type is a nullable type—defined as an instantiation of the value type System.Nullable—the result is a null reference or bitwise copy of its Value property of type T, depending on its HasValue property (false and true, respectively). All boxed types have an operation called unbox, which results in a managed pointer to the bit representation of the value.
因此,根据定义,对可空类型的 box
操作会产生空引用或存储的值,而不是 "boxed nullable"。