[英](Avoid) boxing of is null
給出以下IsNull
方法:
public class Field<T>
{
public static bool IsNull(T value) => value is null; // or: => value == null;
}
當T
是值類型時是否有任何裝箱? 如果答案是肯定的,不裝箱怎么實現? 編輯:它應該處理可為空的類型,例如int?
正確。
Edit2:使用ILDASM
顯示value is null
和value == null
的裝箱:
.method public hidebysig static bool IsNull(!T 'value') cil managed
{
// Code size 15 (0xf)
.maxstack 2
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: box !T
IL_0007: ldnull
IL_0008: ceq
IL_000a: stloc.0
IL_000b: br.s IL_000d
IL_000d: ldloc.0
IL_000e: ret
} // end of method Field`1::IsNull
但是,以下代碼:
public class Field
{
public static bool IsNull(int? value)
{
return value == null;
}
}
將編譯為以下不裝箱:
.method public hidebysig static bool IsNull(valuetype [System.Runtime]System.Nullable`1<int32> 'value') cil managed
{
// Code size 16 (0x10)
.maxstack 2
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldarga.s 'value'
IL_0003: call instance bool valuetype [System.Runtime]System.Nullable`1<int32>::get_HasValue()
IL_0008: ldc.i4.0
IL_0009: ceq
IL_000b: stloc.0
IL_000c: br.s IL_000e
IL_000e: ldloc.0
IL_000f: ret
} // end of method Field::IsNull
所以編譯器確實特別對待Nullable<>
但不在通用 class 上下文中。
如您更新的問題所示:
public static bool IsNull(T value) => value is null;
編譯為以下 IL 代碼:
IL_0000: ldarg.0
IL_0001: box !T
IL_0006: ldnull
IL_0007: ceq
IL_0009: ret
你確實看到了一些拳擊。
然而,這還不是全部。 我們需要看看 JITter 做了什么,即它是如何轉換為本機代碼的。 JITter 將為每個使用的 T 值生成一個單獨的方法。
string
對於string
(可為空的類型),將生成以下本機代碼:
test ecx, ecx
sete al
movzx eax, al
ret
對字符串 object 的引用在寄存器 ECX 中,檢查它是否為 0 (null) [ test ecx, ecx
實際上與cmp ecx,0
相同],並取決於結果 0 (false) 或 1 (true ) 被退回。
int
對於像int
這樣的值類型,會生成以下本機代碼:
xor eax, eax
ret
它只返回 0(假)。
重要的部分是,對於像int
這樣的值類型,沒有裝箱或它的本機等價物實際上會發生,因為運行時知道值類型永遠不會是 null,因此該方法總是只返回 false。
int?
只是為了完整性,對於像int?
我們得到:
cmp byte ptr [esp+4], 0
sete al
movzx eax, al
ret 8
如果 object 實際具有值,即不是 null,則可空值類型在內部只是保存實際值和標志的結構。 因此,在本機代碼中,它訪問標志(您可以看到 int? 存儲在堆棧中,標志位於偏移量 4 處)並將其與 0(假)進行比較。
在這種情況下,您不必擔心裝箱,因為實際上不會創建裝箱的 object。
受System.Text.Json
源代碼的啟發,解決方案如下:
internal static partial class Extensions
{
private static readonly Type s_nullableType = typeof(Nullable<>);
/// <summary>
/// Returns <see langword="true" /> when the given type is of type <see cref="Nullable{T}"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNullableOfT(this Type type) =>
type.IsGenericType && type.GetGenericTypeDefinition() == s_nullableType;
/// <summary>
/// Returns <see langword="true" /> when the given type is either a reference type or of type <see cref="Nullable{T}"/>.
/// </summary>
/// <remarks>This calls <see cref="Type.IsValueType"/> which is slow. If knowledge already exists
/// that the type is a value type, call <see cref="IsNullableOfT"/> instead.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CanBeNull(this Type type) => !type.IsValueType || type.IsNullableOfT();
}
public class Field<T>
{
private static readonly bool s_canBeNull = typeof(T).CanBeNull();
public bool CanBeNull => s_canBeNull;
public virtual bool IsNull(T? value)
{
return IsNullValue(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsNullValue(T? value)
{
// Nullable<> gets boxing here, use NullableField<T> instead.
// Code analysis should generate a warning for using nullable data type of this class
return !s_canBeNull ? false : value == null;
}
}
public class NullableField<T> : Field<Nullable<T>>
where T : struct
{
public sealed override bool IsNull(T? value)
{
return !value.HasValue;
}
}
使用派生的NullableField<T>
class 可以避免裝箱。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.