2024-09-06 (金)
[C#] ジェネリックの T 型が参照型か値型かを判定する
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>() の判定結果の例です。
参考に、以下の結果も記載します。
- Type.IsValueType
- Type.IsClass
- Type.IsInterface
default(T) is null
Category | TypeName (T 型) | IsReference | IsReferenceOrContainsReferences | IsValueType | IsClass | IsInterface | DefaultIsNull |
---|---|---|---|---|---|---|---|
値型 | Int32 | false | false | true | false | false | false |
値型 | MyStruct | false | false | true | false | false | false |
値型 | MyEnum | false | false | true | false | false | false |
値型? | Int32? | false | false | true | false | false | true |
値型? | MyStruct? | false | false | true | false | false | true |
値型? | MyEnum? | false | false | true | false | false | true |
参照型 | String | true | true | false | true | false | true |
参照型 | MyClass | true | true | false | true | false | true |
参照型 | IMyInterface | true | true | false | false | true | true |
参照型を含む | MyReferenceClass | true | true | false | true | false | true |
参照型を含む | MyReferenceStruct | false | true | true | false | false | false |
参照型を含む | MyReferenceStruct? | false | true | true | false | false | true |
上記の 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; }
}
感謝
関連記事
新着記事