Skip to content

Commit

Permalink
Use default binder instead of custom flow for type matching
Browse files Browse the repository at this point in the history
  • Loading branch information
JLChnToZ committed May 30, 2023
1 parent c2ebea3 commit bcde41d
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 127 deletions.
1 change: 1 addition & 0 deletions JLChnToZ.CommonUtils.Dynamic.Test/LimitlessTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ void TestMethodAccess(dynamic limitless, bool isStatic) {
Assert.IsTrue(limitless.PrivateStaticMethod(true), "Access private static method");
if (isStatic) return;
Assert.IsTrue(limitless.PrivateMethod(true), "Access private method");
Assert.IsNull(limitless.PrivateMethod(null), "Access private method with null");
}

void TestGenericMethodAccess(dynamic limitless, bool isStatic) {
Expand Down
27 changes: 10 additions & 17 deletions JLChnToZ.CommonUtils.Dynamic/LimitlessInvokable.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Reflection;

Expand Down Expand Up @@ -57,9 +56,9 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) =>
TryInvoke(args, out result);

bool TryInvoke(object[] args, out object result, IList<Type> genericTypes = null) {
bool TryInvoke(object[] args, out object result) {
var safeArgs = args;
if (TryGetMatchingMethod(methodInfos, ref safeArgs, out var methodInfo, genericTypes)) {
if (TryGetMatchingMethod(methodInfos, ref safeArgs, out var methodInfo)) {
result = InternalWrap(methodInfo.Invoke(target, safeArgs));
InternalWrap(safeArgs, args);
return true;
Expand Down Expand Up @@ -87,21 +86,15 @@ bool TryCreateDelegate(Type delegateType, out object result) {
if (delegateType.IsSubclassOf(typeof(Delegate))) {
var invokeMethod = delegateType.GetMethod("Invoke");
if (invokeMethod != null) {
var expectedParameters = invokeMethod.GetParameters();
var expectedReturnType = invokeMethod.ReturnType;
foreach (var methodInfo in methodInfos) {
if (methodInfo.ContainsGenericParameters ||
methodInfo.ReturnType != expectedReturnType) continue;
var parameters = methodInfo.GetParameters();
if (parameters.Length != expectedParameters.Length) continue;
for (int i = 0; i < parameters.Length; i++)
if (parameters[i].ParameterType != expectedParameters[i].ParameterType)
goto NoMatches;
var matched = Type.DefaultBinder.SelectMethod(
DEFAULT_FLAGS, methodInfos,
Array.ConvertAll(invokeMethod.GetParameters(), GetParameterType), null
) as MethodInfo;
if (matched != null && matched.ReturnType == invokeMethod.ReturnType) {
result = target == null ?
Delegate.CreateDelegate(delegateType, methodInfo, false) :
Delegate.CreateDelegate(delegateType, target, methodInfo, false);
if (result != null) return true;
NoMatches:;
Delegate.CreateDelegate(delegateType, matched, false) :
Delegate.CreateDelegate(delegateType, target, matched, false);
return true;
}
}
}
Expand Down
60 changes: 24 additions & 36 deletions JLChnToZ.CommonUtils.Dynamic/TypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static TypeInfo Get(Type type) {
}

internal bool TryGetValue(object instance, string key, out object value) {
if (properties.TryGetValue(key, out var property)) {
if (properties.TryGetValue(key, out var property) && property.CanRead && property.GetIndexParameters().Length == 0) {
value = InternalWrap(property.GetValue(instance));
return true;
}
Expand All @@ -76,67 +76,55 @@ internal bool TryGetValue(object instance, string key, out object value) {
}

internal bool TryGetValue(object instance, object[] indexes, out object value) {
(PropertyInfo indexer, object[] safeIndexes)? fallback = null;
foreach (var indexer in indexers) {
var safeIndexes = indexes;
switch (UnwrapParamsAndCheck(indexer.GetIndexParameters(), ref safeIndexes)) {
case MethodMatchLevel.Exact:
fallback = (indexer, safeIndexes);
goto skip;
case MethodMatchLevel.Implicit:
if (fallback == null) fallback = (indexer, safeIndexes);
break;
}
}
skip: if (fallback.HasValue) {
value = InternalWrap(fallback.Value.indexer.GetValue(instance, fallback.Value.safeIndexes));
PropertyInfo matched;
if (indexes == null) indexes = emptyArgs;
matched = Type.DefaultBinder.SelectProperty(
DEFAULT_FLAGS | BindingFlags.GetProperty, indexers, null, Array.ConvertAll(indexes, GetUndelyType), null
);
if (matched != null) {
value = InternalWrap(matched.GetValue(instance, InternalUnwrap(
indexes, Type.DefaultBinder,
Array.ConvertAll(matched.GetIndexParameters(), GetParameterType)
)));
return true;
}
value = null;
return false;
}

internal bool TrySetValue(object instance, string key, object value) {
if (properties.TryGetValue(key, out var property)) {
if (properties.TryGetValue(key, out var property) && property.CanWrite && property.GetIndexParameters().Length == 0) {
property.SetValue(InternalUnwrap(instance), value);
return true;
}
if (fields.TryGetValue(key, out var field)) {
if (fields.TryGetValue(key, out var field) && !field.IsInitOnly) {
field.SetValue(InternalUnwrap(instance), value);
return true;
}
return false;
}

internal bool TrySetValue(object instance, object[] indexes, object value) {
(PropertyInfo indexer, object[] safeIndexes)? fallback = null;
foreach (var indexer in indexers) {
var safeIndexes = indexes;
switch (UnwrapParamsAndCheck(indexer.GetIndexParameters(), ref safeIndexes)) {
case MethodMatchLevel.Exact:
fallback = (indexer, safeIndexes);
goto skip;
case MethodMatchLevel.Implicit:
if (fallback == null) fallback = (indexer, safeIndexes);
break;
}
}
skip: if (fallback.HasValue) {
var returnType = fallback.Value.indexer.PropertyType;
if (value == null ? returnType.IsValueType :
!returnType.IsAssignableFrom(value.GetType())) return false;
fallback.Value.indexer.SetValue(InternalUnwrap(instance), value, fallback.Value.safeIndexes);
PropertyInfo matched;
matched = Type.DefaultBinder.SelectProperty(
DEFAULT_FLAGS | BindingFlags.SetProperty, indexers, null, Array.ConvertAll(indexes, GetUndelyType), null
);
if (matched != null) {
matched.SetValue(InternalUnwrap(instance), value, InternalUnwrap(
indexes, Type.DefaultBinder,
Array.ConvertAll(matched.GetIndexParameters(), GetParameterType)
));
return true;
}
return false;
}

internal bool TryGetMethods(string methodName, out MethodInfo[] method) => methods.TryGetValue(methodName, out method);

internal bool TryInvoke(object instance, string methodName, object[] args, out object result, IList<Type> genericTypes = null) {
internal bool TryInvoke(object instance, string methodName, object[] args, out object result) {
var safeArgs = args;
if (methods.TryGetValue(methodName, out var methodArrays) &&
TryGetMatchingMethod(methodArrays, ref safeArgs, out var resultMethod, genericTypes)) {
TryGetMatchingMethod(methodArrays, ref safeArgs, out var resultMethod)) {
result = InternalWrap(resultMethod.Invoke(InternalUnwrap(instance), safeArgs));
InternalWrap(safeArgs, args);
return true;
Expand Down
100 changes: 26 additions & 74 deletions JLChnToZ.CommonUtils.Dynamic/Utilites.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;

namespace JLChnToZ.CommonUtils.Dynamic {
Expand All @@ -11,52 +9,19 @@ internal static class Utilites {
public const BindingFlags DEFAULT_FLAGS = STATIC_FLAGS | INSTANCE_FLAGS;
public static readonly object[] emptyArgs = new object[0];

public static object InternalUnwrap(object obj) => obj is Limitless limitObj ? limitObj.target : obj;

public static object[] InternalUnwrap(object[] objs) {
for (int i = 0; i < objs.Length; i++) objs[i] = InternalUnwrap(objs[i]);
return objs;
}

public static MethodMatchLevel UnwrapParamsAndCheck(ParameterInfo[] paramInfos, ref object[] input, bool shouldCloneArray = false) {
if (input == null) input = emptyArgs;
var currentMatchLevel = MethodMatchLevel.Exact;
if (paramInfos.Length > input.Length) {
var newInput = new object[paramInfos.Length];
Array.Copy(input, newInput, input.Length);
input = newInput;
shouldCloneArray = true;
currentMatchLevel = MethodMatchLevel.Implicit;
}
if (input.Length > paramInfos.Length && paramInfos.Length == 0)
return MethodMatchLevel.NotMatch;
for (int i = 0; i < paramInfos.Length; i++) {
var inputObj = input[i];
var matchLevel = UnwrapParamAndCheck(paramInfos[i].ParameterType, ref inputObj);
if (matchLevel == MethodMatchLevel.NotMatch) return MethodMatchLevel.NotMatch;
input[i] = inputObj;
if (matchLevel < currentMatchLevel) currentMatchLevel = matchLevel;
}
if (currentMatchLevel != MethodMatchLevel.Exact && !shouldCloneArray) {
var newInput = new object[paramInfos.Length];
Array.Copy(input, newInput, input.Length);
input = newInput;
}
return currentMatchLevel;
public static object InternalUnwrap(object obj, Binder binder = null, Type requestedType = null) {
if (obj == null) return null;
if (obj is Limitless limitObj) obj = limitObj.target;
if (binder != null && requestedType != null)
try {
return binder.ChangeType(obj, requestedType, null);
} catch {}
return obj;
}

public static MethodMatchLevel UnwrapParamAndCheck(Type type, ref object input) {
if (input == null) return type.IsValueType ? MethodMatchLevel.NotMatch : MethodMatchLevel.Implicit;
Type inputType, preferredType;
if (input is Limitless limitObj) {
input = limitObj.target;
preferredType = limitObj.type;
inputType = input.GetType();
} else
inputType = preferredType = input.GetType();
return type == preferredType ? MethodMatchLevel.Exact :
type.IsAssignableFrom(inputType) ? MethodMatchLevel.Implicit :
MethodMatchLevel.NotMatch;
public static object[] InternalUnwrap(object[] objs, Binder binder = null, Type[] requestedTypes = null) {
for (int i = 0; i < objs.Length; i++) objs[i] = InternalUnwrap(objs[i], binder, requestedTypes != null ? requestedTypes[i] : null);
return objs;
}

public static object InternalWrap(object obj) =>
Expand All @@ -68,39 +33,26 @@ public static void InternalWrap(object[] sourceObj, object[] destObj) {
for (int i = 0; i < destObj.Length; i++) destObj[i] = InternalWrap(destObj[i]);
}

public static bool TryGetMatchingMethod<T>(T[] methodInfos, ref object[] args, out T bestMatches, IList<Type> genericTypes = null) where T : MethodBase {
(T method, object[] safeArgs)? fallback = null;
foreach (var method in methodInfos) {
var m = method;
var safeArgs = args;
if (method.ContainsGenericParameters) {
var genericArgs = method.GetGenericArguments();
if (genericTypes == null || method.MemberType != MemberTypes.Method || genericArgs.Length != genericTypes.Count) continue;
try {
var typeArgsArray = new Type[genericTypes.Count];
genericTypes.CopyTo(typeArgsArray, 0);
m = (method as MethodInfo).MakeGenericMethod(typeArgsArray) as T;
} catch {
continue;
}
} else if (genericTypes != null && genericTypes.Count > 0) continue;
switch (UnwrapParamsAndCheck(m.GetParameters(), ref safeArgs)) {
case MethodMatchLevel.Exact:
fallback = (m, safeArgs);
goto skip;
case MethodMatchLevel.Implicit:
if (fallback == null) fallback = (m, safeArgs);
break;
}
}
skip: if (fallback.HasValue) {
bestMatches = fallback.Value.method;
args = fallback.Value.safeArgs;
public static bool TryGetMatchingMethod<T>(T[] methodInfos, ref object[] args, out T bestMatches) where T : MethodBase {
MethodBase matched;
if (args == null) args = emptyArgs;
matched = Type.DefaultBinder.SelectMethod(DEFAULT_FLAGS, methodInfos, Array.ConvertAll(args, GetUndelyType), null);
if (matched != null) {
bestMatches = (T)matched;
args = InternalUnwrap(args, Type.DefaultBinder, Array.ConvertAll(matched.GetParameters(), GetParameterType));
return true;
}
bestMatches = null;
return false;
}

public static Type GetUndelyType(object obj) {
if (obj == null) return typeof(object);
if (obj is Limitless limitObj) return limitObj.type;
return obj.GetType();
}

public static Type GetParameterType(ParameterInfo parameterInfo) => parameterInfo.ParameterType;
}

internal enum MethodMatchLevel {
Expand Down

0 comments on commit bcde41d

Please sign in to comment.