Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Type returned from Type.GetProperties for nullable strings is just System.string #56322

Closed
gfs opened this issue Jul 26, 2021 · 5 comments
Closed
Labels
untriaged New issue has not been triaged by the area owner

Comments

@gfs
Copy link

gfs commented Jul 26, 2021

Description

Declaring a string as nullable is not respected in .NET 5. If you ask an object about the type of one of its properties or fields that is a string? it will reply that it is a non-nullable string

Run the following in a .NET 5 project with nullable enabled in the csproj.

using System;

namespace ConsoleApp1
{
    class Program
    {
        class Thing
        {
            public string? PropOne { get; set; }
            public string PropTwo { get; set; } = "";
            public bool? BoolOne { get; set; }
            public bool BoolTwo { get; set; }
            public string? FieldOne = null;
            public string FieldTwo = "";
        }
        static void Main(string[] args)
        {
            var props = typeof(Thing).GetProperties();
            foreach(var prop in props)
            {
                Console.WriteLine($"{prop.Name} IsNullable == {IsNullable(prop.PropertyType)}");
            }
            var fields = typeof(Thing).GetFields();
            foreach(var field in fields)
            {
                Console.WriteLine($"{field.Name} IsNullable == {IsNullable(field.FieldType)}");
            }
        }

        static bool IsNullable(Type type) => type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
    }
}

It will yield this:

image

Configuration

.NET 5.0.302
Windows 10 Build 19043
x64 (Hyper-V)

Regression?

I'm not sure if this previously worked.

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Jul 26, 2021
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@gfs
Copy link
Author

gfs commented Jul 26, 2021

area-System.Reflection most likely

@stephentoub
Copy link
Member

stephentoub commented Jul 26, 2021

type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)

string? isn't a Nullable<string>. Nullable<T> is only applicable to value types (see the struct constraint at

public partial struct Nullable<T> where T : struct
). The ? in string? is a compile-time construct that isn't recognized as part of the type system by the runtime.

@gfs gfs closed this as completed Jul 26, 2021
@gfs
Copy link
Author

gfs commented Jul 26, 2021

Thanks. That makes sense.

@svick
Copy link
Contributor

svick commented Jul 26, 2021

Note that .Net 6 will expose information about nullable reference types using reflection, see #29723. If I use that to modify your code like this:

using System;
using System.Reflection;

#nullable enable

namespace ConsoleApp1
{
    class Program
    {
        class Thing
        {
            public string? PropOne { get; set; }
            public string PropTwo { get; set; } = "";
            public bool? BoolOne { get; set; }
            public bool BoolTwo { get; set; }
            public string? FieldOne = null;
            public string FieldTwo = "";
        }

        static void Main(string[] args)
        {
            var nullabilityContext = new NullabilityInfoContext();

            var props = typeof(Thing).GetProperties();
            foreach (var prop in props)
            {
                Console.WriteLine($"{prop.Name} IsNullable<T>: {IsNullableT(prop.PropertyType)}; Nullability: {nullabilityContext.Create(prop).ReadState}");
            }
            var fields = typeof(Thing).GetFields();
            foreach (var field in fields)
            {
                Console.WriteLine($"{field.Name} IsNullable<T>: {IsNullableT(field.FieldType)}; Nullability: {nullabilityContext.Create(field).ReadState}");
            }
        }

        static bool IsNullableT(Type type) => type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
    }
}

Then I get:

PropOne IsNullable<T>: False; Nullability: Nullable
PropTwo IsNullable<T>: False; Nullability: NotNull
BoolOne IsNullable<T>: True; Nullability: Nullable
BoolTwo IsNullable<T>: False; Nullability: NotNull
FieldOne IsNullable<T>: False; Nullability: Nullable
FieldTwo IsNullable<T>: False; Nullability: NotNull

I'm using the latest daily build for this (versioned as rc.1.21376.12 for some reason), but I believe it should be included starting with .Net 6.0 Preview 7.

@ghost ghost locked as resolved and limited conversation to collaborators Aug 25, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

3 participants