using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; namespace System { /* Contains the rarely used fields of Delegate */ sealed class DelegateData { public Type? target_type; public string? method_name; public bool curried_first_arg; } [StructLayout (LayoutKind.Sequential)] partial class Delegate { #region Sync with object-internals.h IntPtr method_ptr; IntPtr invoke_impl; object? _target; IntPtr method; IntPtr delegate_trampoline; IntPtr extra_arg; IntPtr method_code; IntPtr interp_method; IntPtr interp_invoke_impl; MethodInfo? method_info; // Keep a ref of the MethodInfo passed to CreateDelegate. // Used to keep DynamicMethods alive. MethodInfo? original_method_info; DelegateData data; bool method_is_virtual; #endregion protected Delegate (object target, string method) { if (target is null) throw new ArgumentNullException (nameof (target)); if (method is null) throw new ArgumentNullException (nameof (method)); this._target = target; this.data = new DelegateData () { method_name = method }; } protected Delegate (Type target, string method) { if (target is null) throw new ArgumentNullException (nameof (target)); if (target.ContainsGenericParameters) throw new ArgumentException (SR.Arg_UnboundGenParam, nameof (target)); if (method is null) throw new ArgumentNullException (nameof (method)); if (!target.IsRuntimeImplemented ()) throw new ArgumentException (SR.Argument_MustBeRuntimeType, nameof (target)); this.data = new DelegateData () { method_name = method, target_type = target }; } public object? Target => GetTarget (); internal virtual object? GetTarget () => _target; public static Delegate CreateDelegate (Type type, object? firstArgument, MethodInfo method, bool throwOnBindFailure) { return CreateDelegate (type, firstArgument, method, throwOnBindFailure, true)!; } public static Delegate? CreateDelegate (Type type, MethodInfo method, bool throwOnBindFailure) { return CreateDelegate (type, null, method, throwOnBindFailure, false); } static Delegate? CreateDelegate (Type type, object? firstArgument, MethodInfo method, bool throwOnBindFailure, bool allowClosed) { if (type is null) throw new ArgumentNullException (nameof (type)); if (method is null) throw new ArgumentNullException (nameof (method)); if (!(type is RuntimeType rtType)) throw new ArgumentException (SR.Argument_MustBeRuntimeType, nameof (type)); if (!(method is RuntimeMethodInfo || method is System.Reflection.Emit.DynamicMethod)) throw new ArgumentException (SR.Argument_MustBeRuntimeMethodInfo, nameof (method)); if (!rtType.IsDelegate ()) throw new ArgumentException (SR.Arg_MustBeDelegate, nameof (type)); if (!IsMatchingCandidate (type, firstArgument, method, allowClosed, out DelegateData? delegate_data)) { if (throwOnBindFailure) throw new ArgumentException (SR.Arg_DlgtTargMeth); return null; } Delegate? d = CreateDelegate_internal (type, firstArgument, method, throwOnBindFailure); if (d != null) { d.original_method_info = method; d.data = delegate_data!; } return d; } public static Delegate? CreateDelegate (Type type, object target, string method, bool ignoreCase, bool throwOnBindFailure) { if (type is null) throw new ArgumentNullException (nameof (type)); if (target is null) throw new ArgumentNullException (nameof (target)); if (method is null) throw new ArgumentNullException (nameof (method)); if (!(type is RuntimeType rtType)) throw new ArgumentException (SR.Argument_MustBeRuntimeType, nameof (type)); if (!rtType.IsDelegate ()) throw new ArgumentException (SR.Arg_MustBeDelegate, nameof (type)); MethodInfo? info = GetCandidateMethod (type, target.GetType (), method, BindingFlags.Instance, ignoreCase); if (info is null) { if (throwOnBindFailure) throw new ArgumentException (SR.Arg_DlgtTargMeth); return null; } return CreateDelegate_internal (type, null, info, throwOnBindFailure); } public static Delegate? CreateDelegate (Type type, Type target, string method, bool ignoreCase, bool throwOnBindFailure) { if (type is null) throw new ArgumentNullException (nameof (type)); if (target is null) throw new ArgumentNullException (nameof (target)); if (target.ContainsGenericParameters) throw new ArgumentException (SR.Arg_UnboundGenParam, nameof (target)); if (method is null) throw new ArgumentNullException (nameof (method)); if (!(type is RuntimeType rtType)) throw new ArgumentException (SR.Argument_MustBeRuntimeType, nameof (type)); if (!target.IsRuntimeImplemented ()) throw new ArgumentException (SR.Argument_MustBeRuntimeType, nameof (target)); if (!rtType.IsDelegate ()) throw new ArgumentException (SR.Arg_MustBeDelegate, nameof (type)); MethodInfo? info = GetCandidateMethod (type, target, method, BindingFlags.Static, ignoreCase); if (info is null) { if (throwOnBindFailure) throw new ArgumentException (SR.Arg_DlgtTargMeth); return null; } return CreateDelegate_internal (type, null, info, throwOnBindFailure); } static MethodInfo? GetCandidateMethod (Type type, Type target, string method, BindingFlags bflags, bool ignoreCase) { MethodInfo? invoke = type.GetMethod ("Invoke"); if (invoke is null) return null; ParameterInfo [] delargs = invoke.GetParametersInternal (); Type[] delargtypes = new Type [delargs.Length]; for (int i = 0; i < delargs.Length; i++) delargtypes [i] = delargs [i].ParameterType; /* * since we need to walk the inheritance chain anyway to * find private methods, adjust the bindingflags to ignore * inherited methods */ BindingFlags flags = BindingFlags.ExactBinding | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | bflags; if (ignoreCase) flags |= BindingFlags.IgnoreCase; for (Type? targetType = target; targetType != null; targetType = targetType.BaseType) { MethodInfo? mi = targetType.GetMethod (method, flags, null, delargtypes, Array.Empty()); if (mi != null && IsReturnTypeMatch (invoke.ReturnType!, mi.ReturnType!)) { return mi; } } return null; } static bool IsMatchingCandidate (Type type, object? target, MethodInfo method, bool allowClosed, out DelegateData? delegateData) { MethodInfo? invoke = type.GetMethod ("Invoke"); if (invoke == null || !IsReturnTypeMatch (invoke.ReturnType!, method.ReturnType!)) { delegateData = null; return false; } ParameterInfo[] delargs = invoke.GetParametersInternal (); ParameterInfo[] args = method.GetParametersInternal (); bool argLengthMatch; if (target != null) { // delegate closed over target if (!method.IsStatic) // target is passed as this argLengthMatch = (args.Length == delargs.Length); else // target is passed as the first argument to the static method argLengthMatch = (args.Length == delargs.Length + 1); } else { if (!method.IsStatic) { // // Net 2.0 feature. The first argument of the delegate is passed // as the 'this' argument to the method. // argLengthMatch = (args.Length + 1 == delargs.Length); if (!argLengthMatch) // closed over a null reference argLengthMatch = (args.Length == delargs.Length); } else { argLengthMatch = (args.Length == delargs.Length); if (!argLengthMatch) // closed over a null reference argLengthMatch = args.Length == delargs.Length + 1; } } if (!argLengthMatch) { delegateData = null; return false; } bool argsMatch; delegateData = new DelegateData (); if (target != null) { if (!method.IsStatic) { argsMatch = IsArgumentTypeMatchWithThis (target.GetType (), method.DeclaringType!, true); for (int i = 0; i < args.Length; i++) argsMatch &= IsArgumentTypeMatch (delargs [i].ParameterType, args [i].ParameterType); } else { argsMatch = IsArgumentTypeMatch (target.GetType (), args [0].ParameterType); for (int i = 1; i < args.Length; i++) argsMatch &= IsArgumentTypeMatch (delargs [i - 1].ParameterType, args [i].ParameterType); delegateData.curried_first_arg = true; } } else { if (!method.IsStatic) { if (args.Length + 1 == delargs.Length) { // The first argument should match this argsMatch = IsArgumentTypeMatchWithThis (delargs [0].ParameterType, method.DeclaringType!, false); for (int i = 0; i < args.Length; i++) argsMatch &= IsArgumentTypeMatch (delargs [i + 1].ParameterType, args [i].ParameterType); } else { // closed over a null reference argsMatch = allowClosed; for (int i = 0; i < args.Length; i++) argsMatch &= IsArgumentTypeMatch (delargs [i].ParameterType, args [i].ParameterType); } } else { if (delargs.Length + 1 == args.Length) { // closed over a null reference argsMatch = !(args [0].ParameterType.IsValueType || args [0].ParameterType.IsByRef) && allowClosed; for (int i = 0; i < delargs.Length; i++) argsMatch &= IsArgumentTypeMatch (delargs [i].ParameterType, args [i + 1].ParameterType); delegateData.curried_first_arg = true; } else { argsMatch = true; for (int i = 0; i < args.Length; i++) argsMatch &= IsArgumentTypeMatch (delargs [i].ParameterType, args [i].ParameterType); } } } return argsMatch; } static bool IsReturnTypeMatch (Type delReturnType, Type returnType) { bool returnMatch = returnType == delReturnType; if (!returnMatch) { // Delegate covariance if (!returnType.IsValueType && delReturnType.IsAssignableFrom (returnType)) returnMatch = true; } return returnMatch; } static bool IsArgumentTypeMatch (Type delArgType, Type argType) { bool match = delArgType == argType; // Delegate contravariance if (!match) { if (!argType.IsValueType && argType.IsAssignableFrom (delArgType)) match = true; } // enum basetypes if (!match) { if (delArgType.IsEnum && Enum.GetUnderlyingType (delArgType) == argType) match = true; else if (argType.IsEnum && Enum.GetUnderlyingType (argType) == delArgType) match = true; } return match; } static bool IsArgumentTypeMatchWithThis (Type delArgType, Type argType, bool boxedThis) { bool match; if (argType.IsValueType) match = delArgType.IsByRef && delArgType.GetElementType () == argType || (boxedThis && delArgType == argType); else match = delArgType == argType || argType.IsAssignableFrom (delArgType); return match; } protected virtual object? DynamicInvokeImpl (object?[]? args) { if (Method is null) { #nullable disable // FIXME: This code cannot handle null argument values Type[] mtypes = new Type [args.Length]; for (int i = 0; i < args.Length; ++i) { mtypes [i] = args [i].GetType (); } method_info = _target.GetType ().GetMethod (data.method_name, mtypes); #nullable restore } var target = _target; if (data is null) data = CreateDelegateData (); // replace all Type.Missing with default values defined on parameters of the delegate if any MethodInfo? invoke = GetType ().GetMethod ("Invoke"); if (invoke != null && args != null) { ParameterInfo[] delegateParameters = invoke.GetParameters (); for (int i = 0; i < args.Length; i++) { if (args [i] == Type.Missing) { ParameterInfo dlgParam = delegateParameters [i]; if (dlgParam.HasDefaultValue) { args [i] = dlgParam.DefaultValue; } } } } if (Method.IsStatic) { // // The delegate is bound to _target // if (data.curried_first_arg) { if (args is null) { args = new object?[] { target }; } else { Array.Resize (ref args, args.Length + 1); Array.Copy (args, 0, args, 1, args.Length - 1); args [0] = target; } target = null; } } else { if (_target is null && args?.Length > 0) { target = args [0]; Array.Copy (args, 1, args, 0, args.Length - 1); Array.Resize (ref args, args.Length - 1); } } return Method.Invoke (target, args); } public override bool Equals (object? obj) { if (!(obj is Delegate d) || !InternalEqualTypes (this, obj)) return false; // Do not compare method_ptr, since it can point to a trampoline if (d._target == _target && d.Method == Method) { if (d.data != null || data != null) { /* Uncommon case */ if (d.data != null && data != null) return (d.data.target_type == data.target_type && d.data.method_name == data.method_name); else { if (d.data != null) return d.data.target_type is null; if (data != null) return data.target_type is null; return false; } } return true; } return false; } public override int GetHashCode () { MethodInfo? m = Method; return (m != null ? m.GetHashCode () : GetType ().GetHashCode ()) ^ RuntimeHelpers.GetHashCode (_target); } protected virtual MethodInfo GetMethodImpl () { if (method_info != null) return method_info; if (method != IntPtr.Zero) { if (!method_is_virtual) method_info = (MethodInfo) RuntimeMethodInfo.GetMethodFromHandleNoGenericCheck (new RuntimeMethodHandle (method)); else method_info = GetVirtualMethod_internal (); } return method_info; } DelegateData CreateDelegateData () { DelegateData delegate_data = new DelegateData (); if (method_info.IsStatic) { if (_target != null) { delegate_data.curried_first_arg = true; } else { MethodInfo? invoke = GetType ().GetMethod ("Invoke"); if (invoke != null && invoke.GetParametersCount () + 1 == method_info.GetParametersCount ()) delegate_data.curried_first_arg = true; } } return delegate_data; } static bool InternalEqualTypes (object source, object value) { return source.GetType () == value.GetType (); } [MethodImplAttribute (MethodImplOptions.InternalCall)] private protected extern static MulticastDelegate AllocDelegateLike_internal (Delegate d); [MethodImplAttribute (MethodImplOptions.InternalCall)] static extern Delegate? CreateDelegate_internal (Type type, object? target, MethodInfo info, bool throwOnBindFailure); [MethodImplAttribute (MethodImplOptions.InternalCall)] extern MethodInfo GetVirtualMethod_internal (); } }