运行时泛型类型的可为空性(.NET 7.0)

jv2fixgn  于 2023-05-30  发布在  .NET
关注(0)|答案(3)|浏览(197)

如何在运行时确定泛型参数是否可为空?我希望NullabilityInfoContext类可以解决这个问题,但这也不起作用。
有解决办法吗?

using System.Reflection;
namespace ConsoleApp;

public class Item<T>
{
    public required T Value { get; init; }

    public bool IsNullable()
    {
        var nullabilityInfoContext = new NullabilityInfoContext();
        var propertyInfo = this.GetType().GetProperty(nameof(Value));
        var nullabilityInfo = nullabilityInfoContext.Create(propertyInfo);
        return nullabilityInfo.ReadState == NullabilityState.Nullable;
    }
}

internal class Program
{
    static void Main()
    {
        var nullable = new Item<string?>() { Value = null };
        var notnull = new Item<string>() { Value = "" };
        Console.WriteLine($"nullable is nullable: {nullable.IsNullable()}");
        Console.WriteLine($"notnull is nullable: {notnull.IsNullable()}");
        // Output:
        // nullable is nullable: True
        // notnull is nullable: True
    }
}
pokxtpni

pokxtpni1#

这是一个相当棘手但很好的问题。我担心目前你想要实现的是不可能的,当泛型T是引用类型时,你不能检查泛型的显式?为空性。字符串是引用类型。当T是一个值类型时,你的代码工作得很好:

var nullableString = new Item<string?>() { Value = null }; //IsNullable() true 
var notnullString = new Item<string>() { Value = "" }; //IsNullable() true 

var nullableClass = new Item<SomeClass?>() { Value = new SomeClass{ Id = 1 } }; //IsNullable() true 
var notnullClass = new Item<SomeClass>() { Value = new SomeClass{ Id = 1 } }; //IsNullable() true 

var nullableInt = new Item<int?>() { Value = 2 }; //IsNullable() true 
var notnullInt = new Item<int>() { Value = 3 }; //IsNullable() false

你可以阅读NullabilityInfoContext的详细信息here,这是该功能的拉取请求。
泛型类型T的可空性应该由用户跟踪,字段声明List<string?> list或Listlist将具有类型参数为空性信息,但其他List API调用的为空性应由用户跟踪< string>。例如,对于list.Add(T item),item的可空性将被评估如下:
1.如果声明有?即GenericType<T?>(即使T是值类型,它也是可空值类型)
1.当可空性上下文启用并且T的具体类型是ref类型或可空值类型时,T对于GenericType是可空的。(在这种情况下,List.Add(T item)的参数T的可空性对于List和List<string?>示例,我们可能需要此场景的第四个空性状态)
1.当具体T为不可空值类型时,T对于GenericType为NotNull
1.当可空性上下文禁用且T的具体类型为ref类型时,T对于GenericType为Unknown
1.对于开放泛型,其行为与T的ref类型相同
但是,当引用类型不是泛型时,确定引用类型的显式可为空性没有问题。

var nullabilityInfoContext = new NullabilityInfoContext();
var nullPropInfo = typeof(Foobaz).GetProperty(nameof(Foobaz.NullProperty));
var notNullPropInfo = typeof(Foobaz).GetProperty(nameof(Foobaz.NotNullProperty));

var nullabilityInfo1 = nullabilityInfoContext.Create(nullPropInfo);
Console.WriteLine(nullabilityInfo1.ReadState == NullabilityState.Nullable); //True

var nullabilityInfo2 = nullabilityInfoContext.Create(notNullPropInfo);
Console.WriteLine(nullabilityInfo2.ReadState == NullabilityState.Nullable); //False

public class Foobaz
{
    public string? NullProperty { get; set; }
    public string NotNullProperty { get; set; }
}
xqkwcwgp

xqkwcwgp2#

NullabilityInfoContext“提供用于从反射成员填充为空性信息和上下文的API:ParameterInfo、FieldInfo、PropertyInfo和EventInfo。”
它通过查看关联类型本身的反射信息,并通过检查与属性等事物关联的附加元数据(据我所知,是某种隐藏的属性)来实现这一点。
因此,如果您使用真正的Nullable<>泛型类型,如DoubleL的示例中所示,其中int?实际上是Nullable<int>,则在运行时,Value属性返回Nullable<int>
但这对于String这样的引用类型不起作用:
可空引用类型不是新的类类型,而是对现有引用类型的注解。编译器使用这些注解来帮助您查找代码中潜在的空引用错误。不可空引用类型和可空引用类型在运行时没有区别。
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-reference-types#nullable-references-and-static-analysis
那么属性上的属性又是什么呢?好吧,这是绑定到属性,而不是泛型类型。如果包含了足够的类信息,使其知道泛型类型不能为null,则它将查看属性的签名本身,以确定属性是否可为null。例如:

public class Item<T>
    where T : class // This says T should not be null
{
    public required T Value { get; init; } // Not Nullable
    public required T? Value2 { get; init; } // Nullable

但是,如果T是一个引用类型,那么它总是可以为null。在运行时没有足够的可用数据来完成您要做的事情。Item<string>Item<string?>在运行时没有区别。

62lalag4

62lalag43#

你的解决方案是正确的。
这里的问题是,类型“string”是一个引用类型,默认情况下它可以为null。换句话说,“字符串”和“字符串?“,两者都可以为空。
如果你尝试运行你的例子:

var nullable = new Item<int?>() { Value = null };
var notnull = new Item<int>() { Value = 0 };

会很好。

相关问题