Cara menggunakan .NET refleksi untuk memeriksa jenis referensi yang dapat dibatalkan


15

C # 8.0 memperkenalkan jenis referensi yang dapat dibatalkan. Berikut adalah kelas sederhana dengan properti nullable:

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

Apakah ada cara untuk memeriksa properti kelas menggunakan tipe referensi nullable melalui refleksi?


kompilasi dan melihat IL, sepertinya ini menambah [NullableContext(2), Nullable((byte) 0)]ke jenis ( Foo) - sehingga ini apa untuk memeriksa, tapi aku perlu menggali lebih memahami aturan bagaimana menafsirkan itu!
Marc Gravell

4
Ya, tapi itu tidak sepele. Untungnya, ini didokumentasikan .
Jeroen Mostert

ah, saya mengerti; sehingga string? Xtidak mendapat atribut, dan string Ymendapat [Nullable((byte)2)]dengan [NullableContext(2)]di accesor
Marc Gravell

1
Jika suatu tipe hanya berisi nullables (atau non-nullables), maka itu semua diwakili oleh NullableContext. Jika ada campuran, maka Nullablegunakan juga. NullableContextadalah pengoptimalan untuk mencoba dan menghindari keharusan memancarkan Nullablesemua tempat.
canton7

Jawaban:


11

Tampaknya ini berfungsi, setidaknya pada tipe yang saya uji.

Anda harus melewati PropertyInfountuk properti yang Anda minati, dan juga Typeproperti yang ditentukan ( bukan tipe turunan atau induk - harus tipe yang tepat):

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

Lihat dokumen ini untuk detailnya.

Inti umum adalah bahwa properti itu sendiri dapat memiliki [Nullable]atribut di atasnya, atau jika tidak tipe terlampir mungkin memiliki [NullableContext]atribut. Pertama-tama kita mencari [Nullable], maka jika kita tidak menemukannya kita mencari [NullableContext]pada tipe penutup.

Kompiler mungkin menanamkan atribut ke dalam rakitan, dan karena kita mungkin melihat jenis dari rakitan yang berbeda, kita perlu melakukan beban hanya refleksi.

[Nullable]mungkin dipakai dengan array, jika properti itu generik. Dalam hal ini, elemen pertama mewakili properti aktual (dan elemen lebih lanjut mewakili argumen generik). [NullableContext]selalu dipakai dengan satu byte.

Nilai 2sarana "nullable". 1berarti "tidak dapat dibatalkan", dan 0berarti "tidak menyadari".


Benar-benar rumit. Saya baru saja menemukan use case yang tidak tercakup oleh kode ini. antarmuka publik IBusinessRelation : ICommon {}/ public interface ICommon { string? Name {get;set;} }. Jika saya memanggil metode IBusinessRelationdengan Properti Namesaya salah.
gsharp

@ gsharp Ah, saya belum mencobanya dengan antarmuka, atau warisan apa pun. Saya menduga ini adalah perbaikan yang relatif mudah (lihat atribut konteks dari antarmuka dasar): Saya akan mencoba memperbaikinya nanti
canton7

1
tidak masalah Saya hanya ingin menyebutkannya. Barang yang dapat dibatalkan ini membuatku gila ;-)
gsharp

1
@ gsharp Melihat itu, Anda harus melewati jenis antarmuka yang mendefinisikan properti - yaitu ICommon, tidak IBusinessRelation. Setiap antarmuka mendefinisikan sendiri NullableContext. Saya telah mengklarifikasi jawaban saya, dan menambahkan pemeriksaan runtime untuk ini.
canton7
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.