web-dev-qa-db-fra.com

Comment utiliser la réflexion .NET pour vérifier le type de référence nullable

C # 8.0 introduit des types de référence nullables. Voici une classe simple avec une propriété nullable:

public class Foo
{
    public String? Bar { get; set; }
}

Existe-t-il un moyen de vérifier qu'une propriété de classe utilise un type de référence nullable via la réflexion?

14
shadeglare

Cela semble fonctionner, au moins sur les types avec lesquels je l'ai testé.

Vous devez passer le PropertyInfo pour la propriété qui vous intéresse, ainsi que le Type sur lequel cette propriété est définie ( pas un type dérivé ou parent - ce doit être le type exact):

public static bool IsNullable(Type enclosingType, PropertyInfo property)
{
    if (!enclosingType.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly).Contains(property))
        throw new ArgumentException("enclosingType must be the type which defines property");

    var nullable = property.CustomAttributes
        .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
    if (nullable != null && nullable.ConstructorArguments.Count == 1)
    {
        var attributeArgument = nullable.ConstructorArguments[0];
        if (attributeArgument.ArgumentType == typeof(byte[]))
        {
            var args = (ReadOnlyCollection<CustomAttributeTypedArgument>)attributeArgument.Value;
            if (args.Count > 0 && args[0].ArgumentType == typeof(byte))
            {
                return (byte)args[0].Value == 2;
            }
        }
        else if (attributeArgument.ArgumentType == typeof(byte))
        {
            return (byte)attributeArgument.Value == 2;
        }
    }

    var context = enclosingType.CustomAttributes
        .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
    if (context != null &&
        context.ConstructorArguments.Count == 1 &&
        context.ConstructorArguments[0].ArgumentType == typeof(byte))
    {
        return (byte)context.ConstructorArguments[0].Value == 2;
    }

    // Couldn't find a suitable attribute
    return false;
}

Voir ce document pour plus de détails.

L'essentiel est que la propriété elle-même peut avoir un [Nullable] attribut dessus, ou si ce n'est pas le cas, le type englobant peut avoir [NullableContext] attribut. Nous recherchons d'abord [Nullable], alors si nous ne le trouvons pas, nous recherchons [NullableContext] sur le type englobant.

Le compilateur peut incorporer les attributs dans l'assembly, et comme nous pouvons examiner un type d'un assembly différent, nous devons effectuer une charge de réflexion uniquement.

[Nullable] peut être instancié avec un tableau, si la propriété est générique. Dans ce cas, le premier élément représente la propriété réelle (et les autres éléments représentent des arguments génériques). [NullableContext] est toujours instancié avec un seul octet.

Une valeur de 2 signifie "nullable". 1 signifie "non annulable" et 0 signifie "inconscient".

9
canton7