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

huangapple go评论68阅读模式
英文:

Nullability of generic type at runtime (.NET 7.0)

问题

以下是代码部分的中文翻译:

如何在运行时确定泛型参数是否可为 null?我希望 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()}");
        // 输出:
        // nullable is nullable: True
        // notnull is nullable: True
    }
}

请注意,代码中的 """ 是 HTML 编码,表示双引号。

英文:

How can I determine at runtime whether a generic parameter is nullable or not? I was hoping that the NullabilityInfoContext class would solve the problem, but that doesn't work either.

Is there a solution for this?

using System.Reflection;
namespace ConsoleApp;

public class Item&lt;T&gt;
{
    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&lt;string?&gt;() { Value = null };
        var notnull = new Item&lt;string&gt;() { Value = &quot;&quot; };
        Console.WriteLine($&quot;nullable is nullable: {nullable.IsNullable()}&quot;);
        Console.WriteLine($&quot;notnull is nullable: {notnull.IsNullable()}&quot;);
        // Output:
        // nullable is nullable: True
        // notnull is nullable: True
    }
}

答案1

得分: 2

这是一个相当棘手但好的问题。恐怕目前你想实现的并不可行,当泛型 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,这是这一功能的拉取请求。

对于泛型类型 T 的可空性应该由用户来跟踪,例如字段声明 List<string?> listList<string> list 将具有类型参数的可空性信息,但对于其他 List<T> API 调用,用户应该来跟踪其可空性。例如对于 list.Add(T item),item 的可空性将如下评估:

  1. 如果声明带有 ?,即 GenericType<T?>,则 T 可为空(即使 T 是值类型,它也是可空值类型)。

  2. 当启用 nullability 上下文并且 T 的具体类型为引用类型或可空值类型时,对于 GenericType,T 是可为空的。 (在此情况下,List.Add(T item) 的参数 T 的可空性将对 List 和 List<string?> 实例都是可为空的,我们可能希望对这种情况有第四种可空性状态)

  3. 当具体的 T 是非可空值类型时,对于 GenericType,T 是非空的。

  4. 当禁用 nullability 上下文并且 T 的具体类型为引用类型时,对于 GenericType,T 是未知的。

  5. 对于开放泛型,行为与 T 的引用类型相同。

然而,当它们不是泛型时,确定引用类型的显式可空性就没有问题了。

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; }
}
英文:

It is quite tricky yet good question. I'm afraid that currently what you want to achieve is not possible and you cannot check for explicit ? nullability of generics when generic T is a reference type. And string is a reference type. Your code works perfectly fine when T is a value type:

var nullableString = new Item&lt;string?&gt;() { Value = null }; //IsNullable() true 
var notnullString = new Item&lt;string&gt;() { Value = &quot;&quot; }; //IsNullable() true 

var nullableClass = new Item&lt;SomeClass?&gt;() { Value = new SomeClass{ Id = 1 } }; //IsNullable() true 
var notnullClass = new Item&lt;SomeClass&gt;() { Value = new SomeClass{ Id = 1 } }; //IsNullable() true 

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

You can read about NullabilityInfoContext in details here, this is a pull request of this feature.

> The nullability for generic type T should be tracked by the user, a
> field declaration List<string?> list or List< string> list will have
> type parameter nullability info but nullability for other List<T> API
> calls should be tracked by the user. For example for list.Add(T item)
> the nullability of item will be evaluated as follows:
>
> 1. T is Nullable if the declaration has ? i.e. GenericType<T?> (even if T is value type it is nullable value type)
>
> 2. T is Nullable for GenericType<T> when nullability context enabled and the concrete type of T is ref type or nullable value type. (In
> this case the nullability of parameter T of List.Add(T item) will be
> Nullable for both List<string> and List<string?> instance, we might
> want 4th nullability state for this scenario)
>
> 3. T is NotNull for GenericType<T> when concrete T is non-nullable value type
>
> 4. T is Unknown for GenericType<T> when nullability context disabled and the concrete type of T is ref type
>
> 5. For open generics the behavior is the same as ref type of T

However, there are no problems to determine explicit nullability of reference types when they are not generics.

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; }
}

答案2

得分: 1

NullabilityInfoContext 提供了从反射成员(ParameterInfo、FieldInfo、PropertyInfo 和 EventInfo)中填充空值信息和上下文的 API:“Provides APIs for populating nullability information and context from reflection members: ParameterInfo, FieldInfo, PropertyInfo, and 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,它将查看属性的签名本身来决定属性是否可为空。例如:

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?> 之间在运行时没有区别。

英文:

NullabilityInfoContext "Provides APIs for populating nullability information and context from reflection members: ParameterInfo, FieldInfo, PropertyInfo, and EventInfo."

It does this by looking at the reflected information of the associated types themselves, and by inspecting additional metadata (some kind of hidden attributes, as far as I can tell) associated with things like properties.

So if you're using a truly Nullable&lt;&gt; generic type, as in DoubleL's example, where int? is literally a Nullable&lt;int&gt;, then at runtime it is known that the Value property returns a Nullable&lt;int&gt;.

But that doesn't work for reference types like String:

> Nullable reference types aren't new class types, but rather annotations on existing reference types. The compiler uses those annotations to help you find potential null reference errors in your code. There's no runtime difference between a non-nullable reference type and a nullable reference type.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-reference-types#nullable-references-and-static-analysis

So what about the attributes on properties? Well, that's tied to the property, and not the generic type. If you included enough information on the class for it to know that the generic type could not be null, it would look at the property's signature itself to decide whether the property is nullable. For example:

public class Item&lt;T&gt;
    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

But otherwise, it has to assume that if T is a reference type, it can always be null. There's simply not enough data available at runtime to do what you're trying to do. There is no difference at runtime between an Item&lt;string&gt; and an Item&lt;string?&gt;.

答案3

得分: -1

你的解决方案是正确的。

问题在于,“string”类型是引用类型,它可以默认为null。换句话说,“string”和“string?”之间没有区别,两者都可以为null。

如果你尝试运行你的示例代码:

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

将正常工作。

英文:

Your solution is correct.

The problem there is that, type "string" is a reference type which it can be null by-default. In other words, there is no difference between "string" and "string?", both will be nullable.

If you try to run your example with:

var nullable = new Item&lt;int?&gt;() { Value = null };
var notnull = new Item&lt;int&gt;() { Value = 0 };

will work fine.

huangapple
  • 本文由 发表于 2023年5月24日 23:59:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76325426.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定