Find overriding implementations for virtual methods

This commit is contained in:
Koen
2022-12-12 00:15:54 +00:00
parent 2c2a2524de
commit fead7bb41a
10 changed files with 274 additions and 12 deletions
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
@@ -21,5 +24,90 @@ namespace EntityFrameworkCore.Projectables.Extensions
yield return type;
}
public static MethodInfo GetOverridingMethod(this Type derivedType, MethodInfo methodInfo)
{
// We only need to search for virtual instance methods who are not declared on the derivedType
if (derivedType == methodInfo.DeclaringType || methodInfo.IsStatic || !methodInfo.IsVirtual)
{
return methodInfo;
}
if (!derivedType.IsAssignableTo(methodInfo.DeclaringType))
{
throw new ArgumentException("MethodInfo needs to be declared on the type hierarchy", nameof(methodInfo));
}
var derivedMethods = derivedType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var derivedMethodInfo in derivedMethods)
{
if (HasCompatibleSignature(methodInfo, derivedMethodInfo))
{
return derivedMethodInfo;
}
}
// No derived methods were found. Return the original methodInfo
return methodInfo;
static bool HasCompatibleSignature(MethodInfo methodInfo, MethodInfo derivedMethodInfo)
{
if (methodInfo.Name != derivedMethodInfo.Name)
{
return false;
}
var methodParameters = methodInfo.GetParameters();
var derivedMethodParameters = derivedMethodInfo.GetParameters();
if (methodParameters.Length != derivedMethodParameters.Length)
{
return false;
}
// Match all parameters
for (var parameterIndex = 0; parameterIndex < methodParameters.Length; parameterIndex++)
{
var parameter = methodParameters[parameterIndex];
var derivedParameter = derivedMethodParameters[parameterIndex];
if (parameter.ParameterType.IsGenericParameter)
{
if (!derivedParameter.ParameterType.IsGenericParameter)
{
return false;
}
}
else
{
if (parameter.ParameterType != derivedParameter.ParameterType)
{
return false;
}
}
}
// Match the number of generic type arguments
if (methodInfo.IsGenericMethodDefinition)
{
var methodGenericParameters = methodInfo.GetGenericArguments();
if (!derivedMethodInfo.IsGenericMethodDefinition)
{
return false;
}
var derivedGenericArguments = derivedMethodInfo.GetGenericArguments();
if (methodGenericParameters.Length != derivedGenericArguments.Length)
{
return false;
}
}
return true;
}
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -7,6 +8,7 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using EntityFrameworkCore.Projectables.Extensions;
namespace EntityFrameworkCore.Projectables.Services
{
@@ -39,7 +41,10 @@ namespace EntityFrameworkCore.Projectables.Services
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (TryGetReflectedExpression(node.Method, out var reflectedExpression))
// Get the overriding methodInfo based on te type of the received of this expression
var methodInfo = node.Object?.Type.GetOverridingMethod(node.Method) ?? node.Method;
if (TryGetReflectedExpression(methodInfo, out var reflectedExpression))
{
for (var parameterIndex = 0; parameterIndex < reflectedExpression.Parameters.Count; parameterIndex++)
{
@@ -69,7 +74,12 @@ namespace EntityFrameworkCore.Projectables.Services
protected override Expression VisitMember(MemberExpression node)
{
if (TryGetReflectedExpression(node.Member, out var reflectedExpression))
var nodeMember = node.Expression switch {
{ Type: { } } => node.Expression.Type.GetMember(node.Member.Name, node.Member.MemberType, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)[0],
_ => node.Member
};
if (TryGetReflectedExpression(nodeMember, out var reflectedExpression))
{
if (node.Expression is not null)
{
@@ -14,15 +14,15 @@ namespace EntityFrameworkCore.Projectables.Services
{
public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo)
{
var reflectedType = projectableMemberInfo.ReflectedType ?? throw new InvalidOperationException("Expected a valid type here");
var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(reflectedType.Namespace, reflectedType.GetNestedTypePath().Select(x => x.Name), projectableMemberInfo.Name);
var declaringType = projectableMemberInfo.DeclaringType ?? throw new InvalidOperationException("Expected a valid type here");
var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(declaringType.Namespace, declaringType.GetNestedTypePath().Select(x => x.Name), projectableMemberInfo.Name);
var genericArguments = projectableMemberInfo switch {
MethodInfo methodInfo => methodInfo.GetGenericArguments(),
_ => null
};
var expressionFactoryMethod = reflectedType.Assembly.GetType(generatedContainingTypeName)
var expressionFactoryMethod = declaringType.Assembly.GetType(generatedContainingTypeName)
?.GetMethods()
?.FirstOrDefault();
@@ -40,20 +40,20 @@ namespace EntityFrameworkCore.Projectables.Services
if (useMemberBody is not null)
{
var exprProperty = reflectedType.GetProperty(useMemberBody, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var exprProperty = declaringType.GetProperty(useMemberBody, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var lambda = exprProperty?.GetValue(null) as LambdaExpression;
if (lambda is not null)
{
if (projectableMemberInfo is PropertyInfo property &&
lambda.Parameters.Count == 1 &&
lambda.Parameters[0].Type == reflectedType && lambda.ReturnType == property.PropertyType)
lambda.Parameters[0].Type == declaringType && lambda.ReturnType == property.PropertyType)
{
return lambda;
}
else if (projectableMemberInfo is MethodInfo method &&
lambda.Parameters.Count == method.GetParameters().Length + 1 &&
lambda.Parameters.Last().Type == reflectedType &&
lambda.Parameters.Last().Type == declaringType &&
!lambda.Parameters.Zip(method.GetParameters(), (a, b) => a.Type != b.ParameterType).Any())
{
return lambda;
@@ -62,8 +62,8 @@ namespace EntityFrameworkCore.Projectables.Services
}
var fullName = string.Join(".", Enumerable.Empty<string>()
.Concat(new[] { reflectedType.Namespace })
.Concat(reflectedType.GetNestedTypePath().Select(x => x.Name))
.Concat(new[] { declaringType.Namespace })
.Concat(declaringType.GetNestedTypePath().Select(x => x.Name))
.Concat(new[] { projectableMemberInfo.Name }));
throw new InvalidOperationException($"Unable to resolve generated expression for {fullName}.") {