[C#] ジェネリックの T 型が参照型か値型かを判定する

2024-09-06 (金)

Generic の型引数 T の Type が参照型かどうかを判定する方法です。参照型(を含む)の場合は、参照を解放する場面に使用する想定です。
例えば、ArralPool に返す前の参照クリアRemove した際の参照クリア のような場面です。

環境

  • .NET 8.0.8 (SDK 8.0.400)
  • C# 12.0
  • Visual Studio 2022 Version 17.11.2
  • Windows 11 Pro 23H2 22631.4037

判定するコード

// T 型が参照型かどうかだけ判定したければ、こちら
bool IsReference<T>() => !typeof(T).IsValueType && default(T) is null;

// T が値型の場合に、参照型を含む値型かどうかまで判定したければ、こちら (.NET Core 2 以降)
bool IsReferenceOrContainsReferences<T>() => RuntimeHelpers.IsReferenceOrContainsReferences<T>();

IsReference<T>() については、typeof(T).IsValueType で値型かどうか、default(T) is null で参照型またはインターフェイスかを判定しています。
また、以下の判定はどちらも同じですが、処理時間が50~100倍ほど差がありました。

// こちらの方が高速
bool IsReference<T>() => !typeof(T).IsValueType && default(T) is null;

// 以下のように判定することもできますが、50~100倍ほど遅い
bool IsReference<T>() => typeof(T) is { IsClass: true } or { IsInterface: true };

判定の結果例

上記の IsReference<T>(), IsReferenceOrContainsReferences<T>() の判定結果の例です。

参考に、以下の結果も記載します。

CategoryTypeName (T 型)IsReferenceIsReferenceOrContainsReferencesIsValueTypeIsClassIsInterfaceDefaultIsNull
値型Int32falsefalsetruefalsefalsefalse
値型MyStructfalsefalsetruefalsefalsefalse
値型MyEnumfalsefalsetruefalsefalsefalse
値型?Int32?falsefalsetruefalsefalsetrue
値型?MyStruct?falsefalsetruefalsefalsetrue
値型?MyEnum?falsefalsetruefalsefalsetrue
参照型Stringtruetruefalsetruefalsetrue
参照型MyClasstruetruefalsetruefalsetrue
参照型IMyInterfacetruetruefalsefalsetruetrue
参照型を含むMyReferenceClasstruetruefalsetruefalsetrue
参照型を含むMyReferenceStructfalsetruetruefalsefalsefalse
参照型を含むMyReferenceStruct?falsetruetruefalsefalsetrue

上記の LINQPad コードです。

void Main()
{
    new Result[]
    {
        // 値型
        Get<int>("値型"),
        Get<MyStruct>("値型"),
        Get<MyEnum>("値型"),
        // 値型 Nullable
        Get<int?>("値型?"),
        Get<MyStruct?>("値型?"),
        Get<MyEnum?>("値型?"),
        // 参照型
        Get<String>("参照型"),
        Get<MyClass>("参照型"),
        Get<IMyInterface>("参照型"),
        // 参照型を含む
        Get<MyReferenceClass>("参照型を含む"),
        Get<MyReferenceStruct>("参照型を含む"),
        Get<MyReferenceStruct?>("参照型を含む"),
    }.Dump();
}

enum MyEnum;
class MyClass;
struct MyStruct;
interface IMyInterface;

record class MyReferenceClass(MyClass My);
record struct MyReferenceStruct(MyClass My);

ref struct MyRefStruct;
class MyInterfaceClass : IMyInterface;
struct MyInterfaceStruct : IMyInterface;

Result Get<T>(string category)
{
    var type = typeof(T);

    return new Result
    {
        Category = category,
        TypeName = GetNullableTypeName(type),
        IsReference = !type.IsValueType && default(T) is null,
        IsReferenceOrContainsReferences = RuntimeHelpers.IsReferenceOrContainsReferences<T>(),
        IsValueType = type.IsValueType,
        IsClass = type.IsClass,
        IsInterface = type.IsInterface,
        DefaultIsNull = default(T) is null,
    };

    static string GetNullableTypeName(Type type)
    {
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            return $"{Nullable.GetUnderlyingType(type).Name}?";
        }
        return type.Name;
    }
}

record class Result()
{
    public required string Category { get; init; }
    public required string TypeName { get; init; }
    public required bool IsReference { get; init; }
    public required bool IsReferenceOrContainsReferences { get; init; }
    public required bool IsValueType { get; init; }
    public required bool IsClass { get; init; }
    public required bool IsInterface { get; init; }
    public required bool DefaultIsNull { get; init; }
}

感謝

2024-09-06 (金)