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

[API Proposal]: Reflecting to find nullability of a generic return type is unreasonably complicated #110971

Closed
SRNissen opened this issue Dec 27, 2024 · 5 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Reflection

Comments

@SRNissen
Copy link

SRNissen commented Dec 27, 2024

Background and motivation

For my specific problem, I am trying to recursively walk each member of an object to see if a non-nullable type has had null assigned.

E.g.

    class Example
    {
        public string S {get;set;}
    }

    var e = new Example;

    e.S = null!;

    Assert.That(HasBrokenNullInvariant(e), Is.True);

Which has been a blast to write, until I encountered Index Properties - specifically generic index properties, which always claim to be allowed to return null.

Quite probably I'm just missing something, but I'm missing something because this API is currently less than intuitive.

First, I'd love to replace this:

var properties = type.GetProperties( bindingFlags ) // existing call;
var indexProperty = properties.Single(p=>p.GetIndexParameters().Length != 0); //existing call

with:

var properties = type.GetProperties( bindingFlags ) // existing call;
var indexProperty = properties.Single(p=>p.IsIndexProperty);

Once I have the index property, next step looks like:

var nic = new NullabilityInfoContext(); // existing call
var nullabilityInfo = nic.Create(indexProperty);
return nullabilityInfo.ReadState == NullabilityState.Nullable;

Which only works for non-generic index properties. Try this on e.g. a var l = new List<string>() and it'll happily say that l[2] can return null.

API Proposal

I'm hoping for two things here

  • NullabilityInfoContext.Create(PropertyInfo) expanded to also assess nullability of generic index properties
  • Additional methods on NullabilityInfo for methods (and property methods) that lets you query nullability in slightly more natural language.

API Usage

var nic = new NullabilityInfoContext();
var info = nic.Create(propertyInfo for index property, or methodInfo);

here info with additional members usable like:

info.ReturnValue.IsNullable;
info.Parameters[0].IsNullable;
info.Parameters[1].IsNullable;
etc./

Alternative Designs

No response

Risks

No response

@SRNissen SRNissen added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Dec 27, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Dec 27, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

@KalleOlaviNiemitalo
Copy link

This isn't specific to index properties. Rather, this is because typeof(List<string>) == typeof(List<string?>); the Type object is the same regardless of whether the type argument is a nullable reference type, and therefore NullabilityInfoContext cannot know which one you meant.

SharpLab:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public class C {
    public static void Main() {
        NullabilityInfoContext nullabilityInfoContext = new NullabilityInfoContext();

        {
            Type type = typeof(List<string>);
            PropertyInfo property = type.GetProperties().Single(
                p => p.GetIndexParameters().Length == 1);
        
            NullabilityInfo info = nullabilityInfoContext.Create(property);
            Console.WriteLine($"t={info.Type}, et={info.ElementType}, rs={info.ReadState}, ws={info.WriteState}");
        }
        
        {
            Type type = typeof(Box1<object>);
            PropertyInfo property = type.GetProperty(nameof(Box1<object>.Value))!;
        
            NullabilityInfo info = nullabilityInfoContext.Create(property);
            Console.WriteLine($"t={info.Type}, et={info.ElementType}, rs={info.ReadState}, ws={info.WriteState}");
        }
        
        {
            Type type = typeof(Box2<object>);
            PropertyInfo property = type.GetProperty(nameof(Box2<object>.Value))!;
        
            NullabilityInfo info = nullabilityInfoContext.Create(property);
            Console.WriteLine($"t={info.Type}, et={info.ElementType}, rs={info.ReadState}, ws={info.WriteState}");
        }
    }
    
    internal class Box1<T>
    {
        public Box1(T value)
        {
            this.Value = value;
        }
        
        public T Value { get; set; }
    }

    internal class Box2<T> where T : notnull
    {
        public Box2(T value)
        {
            this.Value = value;
        }
        
        public T Value { get; set; }
    }
}

Output:

t=System.String, et=, rs=Nullable, ws=Nullable
t=System.Object, et=, rs=Nullable, ws=Nullable
t=System.Object, et=, rs=NotNull, ws=NotNull

@KalleOlaviNiemitalo
Copy link

The TypeWithAnnotations idea mentioned in #29723 (comment) could solve this. You wouldn't be able to get that from Object.GetType() though, because the information doesn't exist there.

@SRNissen
Copy link
Author

...that sure kicks the legs out from under my project, but thank you for letting me know.

@steveharter
Copy link
Member

This isn't specific to index properties. Rather, this is because typeof(List) == typeof(List<string?>)

Closing. Nullability works with generic members (including indexer properties) provided the generic type is a value type (either wrapped or not with Nullable<T>) but per above, does not support generic reference types due to C# limitations. To help with this, and you own the code, you can constrain the generic parameter to a reference type and use [MaybeNull] - see this helpful link: https://endjin.com/blog/2020/07/dotnet-csharp-8-nullable-references-maybenull.

For this section:

here info with additional members usable like:

info.ReturnValue.IsNullable;
info.Parameters[0].IsNullable;
info.Parameters[1].IsNullable;
etc./

The "ReturnValue" info can be obtained by using the Create(ParameterInfo) method passing in ReturnParameter.

Is the proposal to add a IsNullable bool property? The ReadState\WriteState properties are based on an enum because the value may not be known to be true\false only, so I assume we would throw in the case it is not known? In either case, this is niche and can easily be wrapped through an extension method by the consumer.

@steveharter steveharter closed this as not planned Won't fix, can't repro, duplicate, stale Dec 30, 2024
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Dec 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Reflection
Projects
None yet
Development

No branches or pull requests

3 participants