.NETリフレクションを使用してnull可能な参照型を確認する方法


15

C#8.0では、null許容の参照型が導入されています。次に、null許容プロパティを持つ単純なクラスを示します。

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

クラスのプロパティがリフレクションを介してnull可能な参照型を使用しているかどうかを確認する方法はありますか?


ILをコンパイルして見ると、これはタイプ()に追加さ[NullableContext(2), Nullable((byte) 0)]れているように見えます。そのため、それを確認する必要がありますが、それを解釈する方法のルールを理解するためにさらに掘り下げる必要があります。Foo
Marc Gravell

4
はい、しかしそれは簡単ではありません。幸いにも、それ 文書化されています
Jeroen Mostert、

ああ、分かった; そのためstring? X、属性を取得せず、アクセサをstring Y取得[Nullable((byte)2)][NullableContext(2)]ます
Marc Gravell

1
タイプがいる場合だけ nullables(または非nullables)が含まれ、それがすべてで表すのです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]常に1バイトでインスタンス化されます。

の値2は「null可能」を意味します。1「null不可」を0意味し、「気付かない」を意味します。


それは本当にトリッキーです。このコードでカバーされていないユースケースを見つけました。パブリックインターフェイスIBusinessRelation : ICommon {}/ public interface ICommon { string? Name {get;set;} }IBusinessRelationプロパティでメソッドを呼び出すと、Namefalseになります。
-gsharp

@gsharpああ、私はそれをインターフェイスやあらゆる種類の継承で試したことはありませんでした。私はそれが比較的簡単な修正だと思います(ベースインターフェースのコンテキスト属性を見てください):後で修正してみます
canton7

1
大したことはありません。私はそれについて言及したかっただけです。このnullableなものは私を狂わせています;-)
gsharp '29年

1
@gsharpこれを見ると、プロパティを定義するインターフェイスのタイプを渡す必要があります。つまり、ICommonではありませんIBusinessRelation。各インターフェースは独自のインターフェースを定義しますNullableContext。私は私の答えを明確にし、これの実行時チェックを追加しました。
canton7
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.