vendredi 29 octobre 2021

Why does Type.GetField may return null when the field indeed exists? [duplicate]

In the following class diagram, you can see that in the hierarchy there are two fields:

  • Location which is protected
  • Settings which is private

enter image description here

While performing reflection to get information about these fields using:

GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)

No matter how hard I'd try, the method would always return null for Settings; this, unless I change its visibility to something else than private; that doesn't makes sense at all because it simply works for other fields in the hierarchy that are private.

Also, according the docs, FlattenHierarchy is of no help since it targets static members.

The environment I'm doing this in is Unity 2021.2 which uses stock .NET Standard 2.1, well, at least that's my theory as the DLL hash is exactly the same as the one in:

C:\Program Files\dotnet\packs\NETStandard.Library.Ref\2.1.0\ref\netstandard2.1\netstandard.dll

In an attempt to solve this problem, I skimmed through Unity sources and stumbled upon an equivalent of the following code:

static FieldInfo? GetField(Type type, string name)
{
    var current = type;

    while (current != null)
    {
        var field = current.GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

        if (field != null)
        {
            return field;
        }

        current = current.BaseType;
    }

    return null;
}

This obvisouly works as that piece of code searches on the base types as well for the field.

Though my problem is solved with this code, it's a bit mysterious as on why is this really needed.

Actually I need this to fetch attributes out of a SerializedProperty:

using System;
using System.Reflection;
using JetBrains.Annotations;
using UnityEditor;

namespace abcd
{
    public static class SerializedPropertyExtensions
    {
        public static T GetPropertyAttribute<T>(this SerializedProperty property) where T : Attribute
        {
            if (property == null)
                throw new ArgumentNullException(nameof(property));

            var info = property.GetPropertyInfoPrivate();

            var attribute = info.FieldInfo?.GetCustomAttribute<T>();

            return attribute;
        }

        private static (object? Parent, FieldInfo FieldInfo, object Value, Type Type) GetPropertyInfoPrivate(this SerializedProperty property)
        {
            if (property == null)
                throw new ArgumentNullException(nameof(property));

            var parent = (object)property.serializedObject.targetObject;
            var field  = default(FieldInfo);
            var value  = (object)property.serializedObject.targetObject;
            var type   = value.GetType();
            var split  = property.propertyPath.Split('.');

            foreach (var name in split)
            {
                parent = value;
                field  = GetField(type, name);
                value  = field.GetValue(value);
                type   = field.FieldType;
            }

            return (parent, field!, value, type);
        }

        private static FieldInfo GetField(Type type, string name)
        {
            var current = type;

            while (current != null)
            {
                var field = current.GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

                if (field != null)
                {
                    return field;
                }

                current = current.BaseType;
            }

            throw new InvalidOperationException();
        }
    }
}

The actual implementation of the above diagram is as follows (trimmed):

internal abstract class BaseImporter : ScriptedImporter
{
    [SerializeField]
    protected string Location = null!;
}

internal abstract class BaseImporterAtlas : BaseImporter
{
    [SerializeField]
    private BaseImporterAtlasSettings Settings = null!;
}

[Serializable]
internal class BaseImporterAtlasSettings
{
    [Min(0)]
    public int Padding;

    [Min(0)]
    public int Bleeding;

    public bool Translucency;

    public BaseImporterAtlasSettings(int padding, int bleeding, bool translucency)
    {
        Padding      = padding;
        Bleeding     = bleeding;
        Translucency = translucency;
    }
}

Question:

Why does a plain call to GetField(...) returns null in this case while it shouldn't really?

Is this a bug or am I missing something in the docs of GetField?





Aucun commentaire:

Enregistrer un commentaire