Last active
May 17, 2024 12:25
-
-
Save JLChnToZ/f97fa95fa3bbce70a789afa897858337 to your computer and use it in GitHub Desktop.
The dynamic access pass to any methods, properties and fields of any types and instances in C#.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Dynamic; | |
using System.Reflection; | |
using System.Runtime.CompilerServices; | |
namespace JLChnToZ.CommonUtils.Dynamic { | |
using static LimitlessUtilities; | |
/// <summary> | |
/// The dynamic access pass to any methods, properties and fields of any types and instances, whatever it is public or private, static or non-static. | |
/// </summary> | |
/// <remarks> | |
/// You can use the static methods from this class as a starting point. | |
/// To initiate the dynamic instance with specified types, you can use <see cref="Type"/> object, type name strings or other wrapped dynamic objects. | |
/// </remarks> | |
/// <example><code><![CDATA[ | |
/// // To construct an instance and call a method | |
/// var newString = Limitless.Construct(typeof(StringBuilder), "Hello, ").Append("World!").ToString(); | |
/// // To call a static generic method, you may use .of(...) to specify generic type arguments. | |
/// Limitless.Static("UnityEngine.Object, UnityEngine").FindObjectOfType<RigidBody>(); | |
/// Limitless.Static("UnityEngine.Object, UnityEngine").FindObjectOfType.of(typeof(Rigidbody))(); | |
/// // To wrap an existing object. | |
/// var obj = new MyObject(); | |
/// var objWrapped = Limitless.Wrap(obj); | |
/// // To re-wrap an object narrowed to a type, useful for accessing hidden interface members | |
/// var narrowedType = Lmitless.Wrap(obj, typeof(IMyInterface)); | |
/// var narrowedType2 = Lmitless.Wrap(objWrapped, typeof(IMyInterface)); | |
/// // To wrap a method to a delegate | |
/// Func<double, double> absWrapped = Limitless.Static(typeof(Math)).Abs; | |
/// ]]></code></example> | |
public class Limitless : DynamicObject, IEnumerable { | |
protected readonly internal object target; | |
protected readonly internal Type type; | |
protected readonly internal TypeInfo typeInfo; | |
/// <summary>Get access to all static members of a type.</summary> | |
/// <param name="type">The type to access.</param> | |
/// <returns>The dynamic object with access to all static members of the type.</returns> | |
public static dynamic Static(string typeName) => Static(Type.GetType(typeName, true)); | |
/// <summary>Get access to all static members of a type.</summary> | |
/// <param name="type">The type to access.</param> | |
/// <returns>The dynamic object with access to all static members of the type.</returns> | |
public static dynamic Static(Type type) { | |
if (type == null) throw new ArgumentNullException(nameof(type)); | |
return new Limitless(null, type); | |
} | |
/// <summary>Get access to all static members of a type.</summary> | |
/// <param name="source">The dynamic object of the type to access.</param> | |
/// <returns>The dynamic object with access to all static members of the type.</returns> | |
public static dynamic Static(Limitless source) => Static(source.type); | |
/// <summary>Wrap an object to dynamic object.</summary> | |
/// <param name="obj">The object to wrap.</param> | |
/// <param name="type">Optional base/interface type to wrap.</param> | |
/// <returns>The dynamic object with access to all members of it.</returns> | |
public static dynamic Wrap(object obj, Type type = null) { | |
if (obj == null) return null; | |
if (obj is DynamicObject dynamicObj) return dynamicObj; | |
if (type == null) return InternalWrap(obj); | |
obj = InternalUnwrap(obj); | |
var objType = obj.GetType(); | |
if (!type.IsAssignableFrom(objType) && !objType.IsAssignableFrom(type)) | |
throw new ArgumentException("Type mismatch", nameof(type)); | |
return new Limitless(obj, type); | |
} | |
/// <summary>Wrap an object to dynamic object.</summary> | |
/// <param name="obj">The object to wrap.</param> | |
/// <param name="typeName">The name of base/interface type to wrap.</param> | |
/// <returns>The dynamic object with access to all members of it.</returns> | |
public static dynamic Wrap(object obj, string typeName) => Wrap(obj, Type.GetType(typeName, true)); | |
/// <summary>Construct an object with specified type and arguments.</summary> | |
/// <param name="type">The type of object to construct.</param> | |
/// <param name="args">The arguments to construct the object.</param> | |
/// <returns>The dynamic instance with access to all members of it.</returns> | |
public static dynamic Construct(Type type, params object[] args) { | |
if (type == null) throw new ArgumentNullException(nameof(type)); | |
if (TypeInfo.Get(type).TryConstruct(args, out var result)) return result; | |
throw new MissingMethodException("No constructor matched"); | |
} | |
/// <summary>Construct an object with specified type and arguments.</summary> | |
/// <param name="typeName">The name of type of object to construct.</param> | |
/// <param name="args">The arguments to construct the object.</param> | |
/// <returns>The dynamic instance with access to all members of it.</returns> | |
public static dynamic Construct(string typeName, params object[] args) => Construct(Type.GetType(typeName, true), args); | |
/// <summary>Construct an object with specified type and arguments.</summary> | |
/// <param name="source">The dynamic object of the type to construct.</param> | |
/// <param name="args">The arguments to construct the object.</param> | |
/// <returns>The dynamic instance with access to all members of it.</returns> | |
public static dynamic Construct(Limitless source, params object[] args) => Construct(source.type, args); | |
protected internal Limitless(object target = null, Type type = null) { | |
this.target = target; | |
this.type = type ?? target?.GetType() ?? typeof(object); | |
typeInfo = TypeInfo.Get(this.type); | |
} | |
public override bool TryGetMember(GetMemberBinder binder, out object result) { | |
if (typeInfo.TryGetValue(target, binder.Name, out result)) | |
return true; | |
if (typeInfo.TryGetMethods(binder.Name, out var methods)) { | |
result = new LimitlessInvokable(target, methods); | |
return true; | |
} | |
if (typeInfo.TryGetSubType(binder.Name, out var subType)) { | |
result = Static(subType); | |
return true; | |
} | |
return typeInfo.TryGetValue(target, new [] { binder.Name }, out result); | |
} | |
public override bool TrySetMember(SetMemberBinder binder, object value) => | |
typeInfo.TrySetValue(target, binder.Name, value) || | |
typeInfo.TrySetValue(target, new [] { binder.Name }, value); | |
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) => | |
typeInfo.TryGetValue(target, indexes, out result); | |
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) => | |
typeInfo.TrySetValue(target, indexes, value); | |
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) => | |
typeInfo.TryInvoke(target, binder.Name, args, out result, binder.GetGenericTypeArguments()); | |
public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) { | |
if (target is Delegate del) { | |
InternalUnwrap(args); | |
result = InternalWrap(del.DynamicInvoke(args)); | |
InternalWrap(args, args); | |
return true; | |
} | |
result = null; | |
return false; | |
} | |
public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) => | |
typeInfo.TryInvoke(target, $"op_{binder.Operation}", new[] { arg }, out result); | |
public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result) => | |
typeInfo.TryInvoke(target, $"op_{binder.Operation}", null, out result); | |
public override bool TryConvert(ConvertBinder binder, out object result) { | |
if (typeInfo.TryCast(target, binder.Type, out result)) | |
return true; | |
try { | |
if (target is IConvertible convertible) { | |
result = convertible.ToType(binder.Type, null); | |
return true; | |
} | |
} catch (Exception) { } | |
result = null; | |
return false; | |
} | |
// Supports await ... syntax | |
public virtual LimitlessAwaiter GetAwaiter() => | |
new LimitlessAwaiter(typeInfo.TryInvoke(target, nameof(GetAwaiter), null, out var awaiter) ? InternalUnwrap(awaiter) : target); | |
// Supports foreach (... in ...) { ... } syntax | |
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |
protected virtual IEnumerator GetEnumerator() { | |
if (target is IEnumerable enumerable) return new LimitlessEnumerator(enumerable.GetEnumerator()); | |
throw new InvalidOperationException("The object is not enumerable."); | |
} | |
public new Type GetType() => target?.GetType() ?? type; | |
public override string ToString() => target?.ToString() ?? type.ToString(); | |
public override int GetHashCode() => target?.GetHashCode() ?? 0; | |
} | |
/// <summary>Special dynamic object that can be used to enumerate.</summary> | |
/// <remarks>This should not be directly used. Use <see cref="Limitless"/> instead.</remarks> | |
public class LimitlessEnumerator : Limitless, IEnumerator { | |
internal LimitlessEnumerator(IEnumerator target) : base(target, null) { } | |
public dynamic Current => InternalWrap(((IEnumerator)target).Current); | |
public bool MoveNext() => ((IEnumerator)target).MoveNext(); | |
public void Reset() => ((IEnumerator)target).Reset(); | |
protected override IEnumerator GetEnumerator() => this; | |
} | |
/// <summary>Special dynamic object that can be used to await for async methods.</summary> | |
/// <remarks>This should not be directly used. Use <see cref="Limitless"/> instead.</remarks> | |
public class LimitlessAwaiter : Limitless, INotifyCompletion { | |
internal LimitlessAwaiter(object target) : base(target, null) { } | |
void INotifyCompletion.OnCompleted(Action continuation) { | |
if (target is INotifyCompletion notifyCompletion) | |
notifyCompletion.OnCompleted(continuation); | |
else | |
continuation(); | |
} | |
public dynamic GetResult() { | |
if (target is INotifyCompletion && typeInfo.TryInvoke(target, nameof(GetResult), emptyArgs, out var result)) | |
return result; | |
return InternalWrap(target); | |
} | |
public override LimitlessAwaiter GetAwaiter() => this; | |
} | |
/// <summary>Special dynamic object that can be used to invoke methods with same name but different signatures.</summary> | |
/// <remarks>This should not be directly used. Use <see cref="Limitless"/> instead.</remarks> | |
public class LimitlessInvokable : DynamicObject { | |
readonly MethodInfo[] methodInfos; | |
readonly object target; | |
internal LimitlessInvokable(object target, MethodInfo[] methodInfos) { | |
if (methodInfos == null || methodInfos.Length == 0) | |
throw new ArgumentNullException(nameof(methodInfos)); | |
this.target = target; | |
this.methodInfos = methodInfos; | |
} | |
public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) => | |
TryInvoke(args, out result, binder.GetGenericTypeArguments()); | |
bool TryInvoke(object[] args, out object result, IList<Type> genericTypes = null) { | |
var safeArgs = args; | |
if (TryGetMatchingMethod(methodInfos, ref safeArgs, out var methodInfo, genericTypes)) { | |
result = InternalWrap(methodInfo.Invoke(target, safeArgs)); | |
InternalWrap(safeArgs, args); | |
return true; | |
} | |
result = null; | |
return false; | |
} | |
/// <summary>Invokes the method with the given arguments.</summary> | |
public dynamic Invoke(params object[] args) { | |
if (TryInvoke(args, out var result)) return result; | |
throw new InvalidOperationException("No matching method found."); | |
} | |
public override bool TryConvert(ConvertBinder binder, out object result) => | |
TryCreateDelegate(binder.Type, out result); | |
/// <summary>Creates a delegate with the given type.</summary> | |
public dynamic CreateDelegate(Type delegateType) { | |
if (TryCreateDelegate(delegateType, out var result)) return result; | |
throw new InvalidOperationException("No matching method found."); | |
} | |
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; | |
result = InternalWrap(target == null ? | |
Delegate.CreateDelegate(delegateType, methodInfo, false) : | |
Delegate.CreateDelegate(delegateType, target, methodInfo, false)); | |
if (result != null) return true; | |
NoMatches:; | |
} | |
} | |
} | |
result = null; | |
return false; | |
} | |
/// <summary>Fulfills generic parameters of the methods.</summary> | |
public dynamic Of(params Type[] types) { | |
if (types == null) throw new ArgumentNullException(nameof(types)); | |
var filteredMethods = new List<MethodInfo>(); | |
foreach (var methodInfo in methodInfos) { | |
var genericParams = methodInfo.GetGenericArguments(); | |
if (genericParams.Length != types.Length) continue; | |
try { | |
filteredMethods.Add(types.Length > 0 ? methodInfo.MakeGenericMethod(types) : methodInfo); | |
} catch {} // Try next if it fails | |
} | |
return new LimitlessInvokable(target, filteredMethods.ToArray()); | |
} | |
public dynamic of(params object[] types) { | |
if (types == null) throw new ArgumentNullException(nameof(types)); | |
var convertedTypes = new Type[types.Length]; | |
for (int i = 0; i < types.Length; i++) { | |
var typeLike = types[i]; | |
if (typeLike is Limitless limitless) { | |
if (limitless.target is Type type) { | |
convertedTypes[i] = type; | |
continue; | |
} | |
if (limitless.target == null) { | |
convertedTypes[i] = limitless.type; | |
continue; | |
} | |
} | |
convertedTypes[i] = (Type)typeLike; | |
} | |
return Of(convertedTypes); | |
} | |
} | |
internal static class LimitlessUtilities { | |
public const BindingFlags BASE_FLAGS = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; | |
public const BindingFlags STATIC_FLAGS = BASE_FLAGS | BindingFlags.Static; | |
public const BindingFlags INSTANCE_FLAGS = BASE_FLAGS | BindingFlags.Instance; | |
public const BindingFlags DEFAULT_FLAGS = STATIC_FLAGS | INSTANCE_FLAGS; | |
public static object InternalUnwrap(object obj) => obj is Limitless limitObj ? limitObj.target : obj; | |
public static readonly object[] emptyArgs = new object[0]; | |
static bool isUsingCSharpBinder = true; | |
static Type invokeBinderType; | |
static PropertyInfo getTypeArgsProperty; | |
public static void InternalUnwrap(object[] objs) { | |
for (int i = 0; i < objs.Length; i++) objs[i] = InternalUnwrap(objs[i]); | |
} | |
public static MethodMatchLevel UnwrapParamsAndCheck(ParameterInfo[] paramInfos, ref object[] input, bool shouldCloneArray = false) { | |
if (input == null) input = emptyArgs; | |
if (paramInfos.Length > input.Length) { | |
var newInput = new object[paramInfos.Length]; | |
Array.Copy(input, newInput, input.Length); | |
input = newInput; | |
shouldCloneArray = true; | |
} | |
var currentMatchLevel = MethodMatchLevel.Exact; | |
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 MethodMatchLevel UnwrapParamAndCheck(Type type, ref object input) { | |
if (input == null) return type.IsValueType ? MethodMatchLevel.NotMatch : MethodMatchLevel.Implicit; | |
if (input is Limitless limitObj) input = limitObj.target; | |
var inputType = input.GetType(); | |
return type == inputType ? MethodMatchLevel.Exact : | |
type.IsAssignableFrom(input.GetType()) ? MethodMatchLevel.Implicit : | |
MethodMatchLevel.NotMatch; | |
} | |
public static object InternalWrap(object obj) => | |
obj == null || obj is DynamicObject || Type.GetTypeCode(obj.GetType()) != TypeCode.Object ? obj : new Limitless(obj); | |
public static void InternalWrap(object[] sourceObj, object[] destObj) { | |
if (sourceObj == null || destObj == null) return; | |
if (sourceObj != destObj) Array.Copy(sourceObj, 0, destObj, 0, Math.Min(sourceObj.Length, destObj.Length)); | |
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; | |
var genericArgs = method.GetGenericArguments(); | |
if (genericArgs.Length > 0) { | |
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; | |
return true; | |
} | |
bestMatches = null; | |
return false; | |
} | |
// https://stackoverflow.com/a/5493142, We can get generic type arguments, but needs some reflection. | |
public static IList<Type> GetGenericTypeArguments(this DynamicMetaObjectBinder binder) { | |
try { | |
if (!isUsingCSharpBinder || binder == null) return null; | |
// ICSharpInvokeOrInvokeMemberBinder is internal | |
// We can eat our own dog food here but it would potentially cause recursion. | |
if (invokeBinderType == null) { | |
invokeBinderType = Type.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder", false); | |
if (invokeBinderType == null) { | |
isUsingCSharpBinder = false; | |
return null; // not using C# binder | |
} | |
} | |
if (!binder.GetType().IsAssignableFrom(invokeBinderType)) return null; | |
if (getTypeArgsProperty == null) getTypeArgsProperty = invokeBinderType.GetProperty("TypeArguments"); | |
return getTypeArgsProperty.GetValue(binder) as IList<Type>; | |
} catch { | |
return null; | |
} | |
} | |
} | |
/// <summary>Internal struct that holds type members information.</summary> | |
public readonly struct TypeInfo { | |
static readonly Dictionary<Type, TypeInfo> cache = new Dictionary<Type, TypeInfo>(); | |
readonly ConstructorInfo[] constructors; | |
readonly PropertyInfo[] indexers; | |
readonly Dictionary<string, MethodInfo[]> methods; | |
readonly Dictionary<string, PropertyInfo> properties; | |
readonly Dictionary<string, FieldInfo> fields; | |
readonly Dictionary<Type, MethodInfo> castOperators; | |
readonly Dictionary<string, Type> subTypes; | |
public static TypeInfo Get(Type type) { | |
if (!cache.TryGetValue(type, out var typeInfo)) | |
cache[type] = typeInfo = new TypeInfo(type); | |
return typeInfo; | |
} | |
TypeInfo(Type type) { | |
if (type == null) throw new ArgumentNullException(nameof(type)); | |
methods = new Dictionary<string, MethodInfo[]>(); | |
properties = new Dictionary<string, PropertyInfo>(); | |
fields = new Dictionary<string, FieldInfo>(); | |
castOperators = new Dictionary<Type, MethodInfo>(); | |
subTypes = new Dictionary<string, Type>(); | |
var tempMethods = new Dictionary<string, List<MethodInfo>>(); | |
foreach (var m in type.GetMethods(DEFAULT_FLAGS)) { | |
var methodName = m.Name; | |
if (m.ContainsGenericParameters) { | |
int methodCountIndex = methodName.LastIndexOf('`'); | |
if (methodCountIndex >= 0) methodName = methodName.Substring(0, methodCountIndex); | |
} | |
if (!tempMethods.TryGetValue(methodName, out var list)) | |
tempMethods[methodName] = list = new List<MethodInfo>(); | |
list.Add(m); | |
switch (methodName) { | |
case "op_Implicit": case "op_Explicit": { | |
var parameters = m.GetParameters(); | |
var returnType = m.ReturnType; | |
if (parameters.Length == 1 && returnType != type && returnType != typeof(void)) | |
castOperators[parameters[0].ParameterType] = m; | |
break; | |
} | |
} | |
} | |
foreach (var kv in tempMethods) methods[kv.Key] = kv.Value.ToArray(); | |
var tempIndexers = new List<PropertyInfo>(); | |
foreach (var p in type.GetProperties(DEFAULT_FLAGS)) { | |
properties[p.Name] = p; | |
if (p.GetIndexParameters().Length > 0) tempIndexers.Add(p); | |
} | |
indexers = tempIndexers.ToArray(); | |
foreach (var f in type.GetFields(DEFAULT_FLAGS)) fields[f.Name] = f; | |
constructors = type.GetConstructors(INSTANCE_FLAGS); | |
foreach (var t in type.GetNestedTypes(DEFAULT_FLAGS)) subTypes[t.Name] = t; | |
} | |
internal bool TryGetValue(object instance, string key, out object value) { | |
if (properties.TryGetValue(key, out var property)) { | |
value = InternalWrap(property.GetValue(instance)); | |
return true; | |
} | |
if (fields.TryGetValue(key, out var field)) { | |
value = InternalWrap(field.GetValue(instance)); | |
return true; | |
} | |
value = null; | |
return false; | |
} | |
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)); | |
return true; | |
} | |
value = null; | |
return false; | |
} | |
internal bool TrySetValue(object instance, string key, object value) { | |
if (properties.TryGetValue(key, out var property)) { | |
property.SetValue(InternalUnwrap(instance), value); | |
return true; | |
} | |
if (fields.TryGetValue(key, out var field)) { | |
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) { | |
fallback.Value.indexer.SetValue(InternalUnwrap(instance), value, fallback.Value.safeIndexes); | |
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) { | |
var safeArgs = args; | |
if (methods.TryGetValue(methodName, out var methodArrays) && | |
TryGetMatchingMethod(methodArrays, ref safeArgs, out var resultMethod, genericTypes)) { | |
result = InternalWrap(resultMethod.Invoke(InternalUnwrap(instance), safeArgs)); | |
InternalWrap(safeArgs, args); | |
return true; | |
} | |
result = null; | |
return false; | |
} | |
internal bool TryCast(object instance, Type type, out object result) { | |
// Note: Return results after casting must not be wrapped. | |
instance = InternalUnwrap(instance); | |
if (instance == null) { | |
result = null; | |
return !type.IsValueType; // Only reference types can be null | |
} | |
if (type.IsAssignableFrom(instance.GetType())) { // No need to cast if the type is already assignable | |
result = instance; | |
return true; | |
} | |
if (castOperators.TryGetValue(type, out var method)) { | |
result = method.Invoke(instance, emptyArgs); | |
return true; | |
} | |
result = null; | |
return false; | |
} | |
internal bool TryConstruct(object[] args, out object result) { | |
if (TryGetMatchingMethod(constructors, ref args, out var resultConstructor)) { | |
result = InternalWrap(resultConstructor.Invoke(args)); | |
return true; | |
} | |
result = null; | |
return false; | |
} | |
internal bool TryGetSubType(string name, out Type type) => subTypes.TryGetValue(name, out type); | |
} | |
internal enum MethodMatchLevel { | |
NotMatch, | |
Implicit, | |
Exact, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment