Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for dynamic types like expando object #36 #37

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 95 additions & 63 deletions src/NReco.LambdaParser/InvokeMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,151 +18,183 @@
using System.Text;
using System.Reflection;

namespace NReco {

namespace NReco
{

/// <summary>
/// Invoke object's method that is most compatible with provided arguments
/// </summary>
internal class InvokeMethod {
internal class InvokeMethod
{

public object TargetObject { get; set; }

public string MethodName { get; set; }

public InvokeMethod(object o, string methodName) {
public InvokeMethod(object o, string methodName)
{
TargetObject = o;
MethodName = methodName;
}

protected MethodInfo FindMethod(Type[] argTypes) {
if (TargetObject is Type) {
protected MethodInfo FindMethod(Type[] argTypes)
{
if (TargetObject is Type)
{
// static method
#if NET40
#if NET40
return ((Type)TargetObject).GetMethod(MethodName, BindingFlags.Static | BindingFlags.Public);
#else
return ((Type)TargetObject).GetRuntimeMethod(MethodName, argTypes);
#endif
#else
return ((Type) TargetObject).GetRuntimeMethod(MethodName, argTypes);
#endif
}
#if NET40
#if NET40
return TargetObject.GetType().GetMethod(MethodName, argTypes);
#else
#else
return TargetObject.GetType().GetRuntimeMethod(MethodName, argTypes);
#endif
#endif
}

protected IEnumerable<MethodInfo> GetAllMethods() {
if (TargetObject is Type) {
#if NET40
protected IEnumerable<MethodInfo> GetAllMethods()
{
if (TargetObject is Type)
{
#if NET40
return ((Type)TargetObject).GetMethods(BindingFlags.Static | BindingFlags.Public);
#else
return ((Type)TargetObject).GetRuntimeMethods();
#endif
#else
return ((Type) TargetObject).GetRuntimeMethods();
#endif
}
#if NET40
#if NET40
return TargetObject.GetType().GetMethods();
#else
#else
return TargetObject.GetType().GetRuntimeMethods();
#endif
#endif
}

public object Invoke(object[] args) {
public object Invoke(object[] args)
{
Type[] argTypes = new Type[args.Length];
for (int i = 0; i < argTypes.Length; i++)
argTypes[i] = args[i] != null ? args[i].GetType() : typeof(object);

// strict matching first
MethodInfo targetMethodInfo = FindMethod(argTypes);
// fuzzy matching
if (targetMethodInfo==null) {
if (targetMethodInfo == null)
{
var methods = GetAllMethods();

foreach (var m in methods)
if (m.Name==MethodName &&
m.GetParameters().Length == args.Length &&
CheckParamsCompatibility(m.GetParameters(), argTypes, args)) {
if (m.Name == MethodName &&
m.GetParameters().Length == args.Length &&
CheckParamsCompatibility(m.GetParameters(), argTypes, args))
{
targetMethodInfo = m;
break;
}
}
if (targetMethodInfo == null) {

if (targetMethodInfo == null)
{
string[] argTypeNames = new string[argTypes.Length];
for (int i=0; i<argTypeNames.Length; i++)
for (int i = 0; i < argTypeNames.Length; i++)
argTypeNames[i] = argTypes[i].Name;
string argTypeNamesStr = String.Join(",",argTypeNames);
string argTypeNamesStr = String.Join(",", argTypeNames);
throw new MissingMemberException(
(TargetObject is Type ? (Type)TargetObject : TargetObject.GetType()).FullName+"."+MethodName );
(TargetObject is Type ? (Type) TargetObject : TargetObject.GetType()).FullName + "." + MethodName);
}
object[] argValues = PrepareActualValues(targetMethodInfo.GetParameters(),args);

object[] argValues = PrepareActualValues(targetMethodInfo.GetParameters(), args);
object res = null;
try {
res = targetMethodInfo.Invoke( TargetObject is Type ? null : TargetObject, argValues);
} catch (TargetInvocationException tiEx) {
if (tiEx.InnerException!=null)
try
{
res = targetMethodInfo.Invoke(TargetObject is Type ? null : TargetObject, argValues);
}
catch (TargetInvocationException tiEx)
{
if (tiEx.InnerException != null)
throw new Exception(tiEx.InnerException.Message, tiEx.InnerException);
else {
else
{
throw;
}
}

return res;
}

internal static bool IsInstanceOfType(Type t, object val) {
#if NET40
internal static bool IsInstanceOfType(Type t, object val)
{
#if NET40
return t.IsInstanceOfType(val);
#else
return val!=null && t.GetTypeInfo().IsAssignableFrom(val.GetType().GetTypeInfo());
#endif
#else
return val != null && t.GetTypeInfo().IsAssignableFrom(val.GetType().GetTypeInfo());
#endif
}

protected bool CheckParamsCompatibility(ParameterInfo[] paramsInfo, Type[] types, object[] values) {
for (int i=0; i<paramsInfo.Length; i++) {
protected bool CheckParamsCompatibility(ParameterInfo[] paramsInfo, Type[] types, object[] values)
{
for (int i = 0; i < paramsInfo.Length; i++)
{
Type paramType = paramsInfo[i].ParameterType;
var val = values[i];
if (IsInstanceOfType(paramType, val))
continue;
// null and reference types
if (val==null &&
#if NET40
if (val == null &&
#if NET40
!paramType.IsValueType
#else
!paramType.GetTypeInfo().IsValueType
#endif
#else
!paramType.GetTypeInfo().IsValueType
#endif
)
continue;
// possible autocast between generic/non-generic common types
try {
try
{
Convert.ChangeType(val, paramType, System.Globalization.CultureInfo.InvariantCulture);
continue;
} catch { }
}
catch
{
}

//if (ConvertManager.CanChangeType(types[i],paramType))
// continue;
// incompatible parameter
return false;
}

return true;
}


protected object[] PrepareActualValues(ParameterInfo[] paramsInfo, object[] values) {
protected object[] PrepareActualValues(ParameterInfo[] paramsInfo, object[] values)
{
object[] res = new object[paramsInfo.Length];
for (int i=0; i<paramsInfo.Length; i++) {
if (values[i]==null || IsInstanceOfType( paramsInfo[i].ParameterType, values[i])) {
for (int i = 0; i < paramsInfo.Length; i++)
{
if (values[i] == null || IsInstanceOfType(paramsInfo[i].ParameterType, values[i]))
{
res[i] = values[i];
continue;
}
try {
res[i] = Convert.ChangeType( values[i], paramsInfo[i].ParameterType, System.Globalization.CultureInfo.InvariantCulture );

try
{
res[i] = Convert.ChangeType(values[i], paramsInfo[i].ParameterType,
System.Globalization.CultureInfo.InvariantCulture);
continue;
} catch {
throw new InvalidCastException(
}
catch
{
throw new InvalidCastException(
String.Format("Invoke method '{0}': cannot convert argument #{1} from {2} to {3}",
MethodName, i, values[i].GetType(), paramsInfo[i].ParameterType));
}
}

return res;
}


}

}
}
9 changes: 9 additions & 0 deletions src/NReco.LambdaParser/Linq/LambdaParameterWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ internal sealed class LambdaParameterWrapper : IComparable, ILambdaValue {
if (obj is LambdaParameterWrapper)
obj = ((LambdaParameterWrapper)obj).Value;

// An epandoObject has a IDictionary underneath
object objectValue;
if (obj is IDictionary<string, object> dictionary && dictionary.TryGetValue(propertyName, out objectValue))
{
return new LambdaParameterWrapper(objectValue, Cmp);
}

#if NET40
var prop = obj.GetType().GetProperty(propertyName);
#else
Expand All @@ -140,6 +147,7 @@ internal sealed class LambdaParameterWrapper : IComparable, ILambdaValue {
var propVal = prop.GetValue(obj, null);
return new LambdaParameterWrapper(propVal, Cmp);
}

#if NET40
var fld = obj.GetType().GetField(propertyName);
#else
Expand All @@ -149,6 +157,7 @@ internal sealed class LambdaParameterWrapper : IComparable, ILambdaValue {
var fldVal = fld.GetValue(obj);
return new LambdaParameterWrapper(fldVal, Cmp);
}

throw new MissingMemberException(obj.GetType().ToString()+"."+propertyName);
}

Expand Down
Loading