簡體   English   中英

(避免)裝箱的是 null

[英](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 nullvalue == 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.

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