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

Open
SRNissen opened this issue Dec 27, 2024 · 3 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Reflection untriaged New issue has not been triaged by the area owner

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.

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 untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

2 participants