Come utilizzare .NET reflection per verificare il tipo di riferimento nullable


15

C # 8.0 introduce tipi di riferimento nullable. Ecco una semplice classe con una proprietà nullable:

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

Esiste un modo per verificare che una proprietà di classe utilizzi un tipo di riferimento nullable tramite reflection?


compilando e guardando l'IL, sembra che questo aggiunga [NullableContext(2), Nullable((byte) 0)]al tipo ( Foo) - quindi questo è cosa cercare, ma dovrei scavare di più per capire le regole su come interpretarlo!
Marc Gravell

4
Sì, ma non è banale. Fortunatamente, è documentato .
Jeroen Mostert,

ah, vedo; quindi string? Xnon ottiene attributi e string Yva d' [Nullable((byte)2)][NullableContext(2)]
accordo

1
Se un tipo contiene solo nullable (o non nullable), allora è tutto rappresentato da NullableContext. Se c'è un mix, anche Nullableusato. NullableContextè un'ottimizzazione per cercare di evitare di dover emettere ovunque Nullable.
canton7,

Risposte:


11

Questo sembra funzionare, almeno sui tipi con cui l'ho provato.

Devi passare il PropertyInfoper la proprietà che ti interessa, e anche il motivo per Typecui quella proprietà è definita ( non un tipo derivato o genitore - deve essere il tipo esatto):

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

Vedi questo documento per i dettagli.

L'essenza generale è che o la proprietà stessa può avere un [Nullable]attributo su di essa, o in caso contrario il tipo racchiuso potrebbe avere un [NullableContext]attributo. Prima cerchiamo [Nullable], quindi se non lo troviamo cerchiamo [NullableContext]nel tipo allegato.

Il compilatore potrebbe incorporare gli attributi nell'assembly e poiché potremmo guardare un tipo da un assembly diverso, dobbiamo eseguire un caricamento di solo riflessione.

[Nullable]potrebbe essere istanziato con un array, se la proprietà è generica. In questo caso, il primo elemento rappresenta la proprietà effettiva (e ulteriori elementi rappresentano argomenti generici). [NullableContext]viene sempre istanziato con un singolo byte.

Un valore di 2significa "nullable". 1significa "non nullable" e 0significa "ignaro".


È davvero complicato. Ho appena trovato un caso d'uso che non è coperto da questo codice. interfaccia pubblica IBusinessRelation : ICommon {}/ public interface ICommon { string? Name {get;set;} }. Se chiamo il metodo IBusinessRelationcon la proprietà Nameottengo falso.
gsharp,

@gsharp Ah, non l'avevo provato con interfacce o eredità. Immagino che sia una soluzione relativamente semplice (guarda gli attributi di contesto dalle interfacce di base): proverò a risolverlo più tardi
canton7

1
nessun problema. Volevo solo menzionarlo. Questa roba nullable mi sta facendo impazzire ;-)
gsharp

1
@gsharp Guardandolo, è necessario passare il tipo di interfaccia che definisce la proprietà, ovvero ICommonno IBusinessRelation. Ogni interfaccia definisce la propria NullableContext. Ho chiarito la mia risposta e ho aggiunto un controllo di runtime per questo.
canton7,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.