dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.19k stars 4.72k forks source link

Expression.Property() throws System.ArgumentException: 'The method 'X' is not a property accessor (Parameter 'propertyAccessor')' #106530

Open voroninp opened 2 months ago

voroninp commented 2 months ago

Description

Method public static MemberExpression Property(Expression? expression, MethodInfo propertyAccessor) throws System.ArgumentException: 'The method 'X' is not a property accessor (Parameter 'propertyAccessor')' for getters reflected via descendant class.

Internally this method looks for the property having same getter as passed method:

[RequiresUnreferencedCode(PropertyFromAccessorRequiresUnreferencedCode)]
public static MemberExpression Property(Expression? expression, MethodInfo propertyAccessor)
{
    ArgumentNullException.ThrowIfNull(propertyAccessor);
    ValidateMethodInfo(propertyAccessor, nameof(propertyAccessor));
    return Property(expression, GetProperty(propertyAccessor, nameof(propertyAccessor)));
}

[RequiresUnreferencedCode(PropertyFromAccessorRequiresUnreferencedCode)]
private static PropertyInfo GetProperty(MethodInfo mi, string? paramName, int index = -1)
{
    Type? type = mi.DeclaringType;
    if (type != null)
    {
        BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic;
        flags |= (mi.IsStatic) ? BindingFlags.Static : BindingFlags.Instance;
        PropertyInfo[] props = type.GetProperties(flags);
        foreach (PropertyInfo pi in props)
        {
            if (pi.CanRead && CheckMethod(mi, pi.GetGetMethod(nonPublic: true)!))
            {
                return pi;
            }
            if (pi.CanWrite && CheckMethod(mi, pi.GetSetMethod(nonPublic: true)!))
            {
                return pi;
            }
        }
    }

    throw Error.MethodNotPropertyAccessor(mi.DeclaringType, mi.Name, paramName, index);
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
    Justification = "Since the methods are already supplied, they won't be trimmed. Just checking for method equality.")]
private static bool CheckMethod(MethodInfo method, MethodInfo propertyMethod)
{
    // WARNING, ALERT, DANGER: These won't equal if getter was acquired via descendant type!!!
    if (method.Equals(propertyMethod))
    {
        return true;
    }
    // If the type is an interface then the handle for the method got by the compiler will not be the
    // same as that returned by reflection.
    // Check for this condition and try and get the method from reflection.
    Type type = method.DeclaringType!;
    if (type.IsInterface && method.Name == propertyMethod.Name && type.GetMethod(method.Name) == propertyMethod)
    {
        return true;
    }
    return false;
}

The only problem is that getters reflected via ancestor and descendant differ! Type? type =mi.DeclaringType; is the core thing.

It should be Type? type = mi.ReflectedType;

Reproduction Steps

See this repo to reproduce.

Method public static MemberExpression Property(Expression? expression, MethodInfo propertyAccessor)

Expected behavior

It should just work.

Actual behavior

If fails.

Regression?

I have no clue.

Known Workarounds

If I use an overload version:

public static MemberExpression Property(Expression? expression, PropertyInfo property)

it works ok. And it's obviously faster because there's no search for the property. But anyway...

Configuration

No response

Other information

No response

dotnet-policy-service[bot] commented 2 months ago

Tagging subscribers to this area: @dotnet/area-system-reflection See info in area-owners.md if you want to be subscribed.

dotnet-policy-service[bot] commented 2 months ago

Tagging subscribers to this area: @cston See info in area-owners.md if you want to be subscribed.