Як використовувати .NET-відображення для перевірки на нульовий тип посилання


15

C # 8.0 вводить зведені типи посилань. Ось простий клас із властивістю до нуля:

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

Чи є спосіб перевірити властивість класу, використовуючи нульовий тип посилання за допомогою відображення?


компілюючи та дивлячись на ІЛ, схоже, це додає [NullableContext(2), Nullable((byte) 0)]до типу ( Foo) - так це на що потрібно перевірити, але мені потрібно більше копатись, щоб зрозуміти правила того, як це інтерпретувати!
Марк Гравелл

4
Так, але це не банально. До щастя, це документально .
Jeroen

А, бачу; тому string? Xне отримує ніяких атрибутів і string Yотримує [Nullable((byte)2)]з [NullableContext(2)]аксесуарами
Marc Gravell

1
Якщо тип просто містить нульові (або не-нульові), то це все представлено символом NullableContext. Якщо є суміш, то також Nullableвикористовується. NullableContextце оптимізація, щоб спробувати уникнути необхідності випромінювати Nullableвсюди.
canton7

Відповіді:


11

Це, здається, працює, принаймні, на типи, з якими я його тестував.

Вам потрібно передати те, PropertyInfoщо вас цікавить, а також те, на Typeякому визначено вказане властивість ( не похідний або батьківський тип - він повинен бути точним типом):

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

Докладніше див. У цьому документі .

Загальна суть полягає в тому, що або власне властивість може мати [Nullable]атрибут на ньому, або, якщо це не тип анотації, що може містити [NullableContext]атрибут. Спочатку ми шукаємо [Nullable], потім, якщо ми не знаходимо його, шукаємо [NullableContext]на типі, що додається.

Компілятор може вбудувати атрибути в збірку, і оскільки ми можемо шукати тип з іншої збірки, нам потрібно виконати завантаження лише для відображення.

[Nullable]може бути примірник масиву, якщо властивість є загальною. У цьому випадку перший елемент представляє фактичну властивість (а подальші елементи представляють загальні аргументи). [NullableContext]завжди інстанціюється одним байтом.

Значення 2означає "нульовий". 1означає "не нульовий", а 0означає "забутий".


Це справді хитро. Щойно я знайшов випадок використання, який не охоплюється цим кодом. публічний інтерфейс IBusinessRelation : ICommon {}/ public interface ICommon { string? Name {get;set;} }. Якщо я викликаю метод IBusinessRelationіз властивістю, Nameя отримую помилковий.
gsharp

@gsharp Ах, я не пробував цього з інтерфейсами чи будь-яким спадщиною. Я здогадуюсь, що це відносно легко виправити (подивіться на атрибути контексту з базових інтерфейсів): я спробую виправити це пізніше
canton7

1
немає великого. Я просто хотів це згадати. Цей мінливий матеріал зводить мене з розуму ;-)
gsharp

1
@gsharp Дивлячись на це, вам потрібно передати тип інтерфейсу, який визначає властивість - тобто ICommon, ні IBusinessRelation. Кожен інтерфейс визначає своє NullableContext. Я уточнив свою відповідь і додав для цього перевірку часу виконання.
кантон7
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.