New test.
[mono.git] / mcs / class / corlib / System / Delegate.cs
index 774aade3a1a40d9955bfcdd27195d53f1351f686..733eac9a4d3f132eab175fecdeed571364b4145f 100644 (file)
 //
 
 using System.Reflection;
+using System.Runtime.Remoting;
 using System.Runtime.Serialization;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
 namespace System
 {
-#if NET_1_1
+       /* Contains the rarely used fields of Delegate */
+       class DelegateData {
+               public Type target_type;
+               public string method_name;
+       }
+
        [ClassInterface (ClassInterfaceType.AutoDual)]
-#endif
+       [System.Runtime.InteropServices.ComVisible (true)]
+       [Serializable]
        public abstract class Delegate : ICloneable, ISerializable
        {
-               private Type target_type;
-               private object m_target;
-               private string method_name;
+               #region Sync with object-internals.h
+#pragma warning disable 169, 414, 649
                private IntPtr method_ptr;
+               private IntPtr invoke_impl;
+               private object m_target;
+               private IntPtr method;
                private IntPtr delegate_trampoline;
+               private IntPtr method_code;
                private MethodInfo method_info;
 
+               // Keep a ref of the MethodInfo passed to CreateDelegate.
+               // Used to keep DynamicMethods alive.
+               private MethodInfo original_method_info;
+
+               private DelegateData data;
+#pragma warning restore 169, 414, 649
+               #endregion
+
                protected Delegate (object target, string method)
                {
                        if (target == null)
@@ -59,10 +77,9 @@ namespace System
                        if (method == null)
                                throw new ArgumentNullException ("method");
 
-                       this.target_type = null;
-                       this.method_ptr = IntPtr.Zero;
                        this.m_target = target;
-                       this.method_name = method;
+                       this.data = new DelegateData ();
+                       this.data.method_name = method;
                }
 
                protected Delegate (Type target, string method)
@@ -73,15 +90,21 @@ namespace System
                        if (method == null)
                                throw new ArgumentNullException ("method");
 
-                       this.target_type = target;
-                       this.method_ptr = IntPtr.Zero;
-                       this.m_target = null;
-                       this.method_name = method;
+                       this.data = new DelegateData ();
+                       this.data.method_name = method;
+                       this.data.target_type = target;
                }
 
                public MethodInfo Method {
                        get {
-                               return method_info;
+                               if (method_info != null) {
+                                       return method_info;
+                               } else {
+                                       if (method != IntPtr.Zero) {
+                                               method_info = (MethodInfo)MethodBase.GetMethodFromHandleNoGenericCheck (new RuntimeMethodHandle (method));
+                                       }
+                                       return method_info;
+                               }
                        }
                }
 
@@ -96,10 +119,40 @@ namespace System
                //
 
                [MethodImplAttribute (MethodImplOptions.InternalCall)]
-               internal static extern Delegate CreateDelegate_internal (Type type, object target, MethodInfo info);
+               internal static extern Delegate CreateDelegate_internal (Type type, object target, MethodInfo info, bool throwOnBindFailure);
+
+               [MethodImplAttribute (MethodImplOptions.InternalCall)]
+               internal extern void SetMulticastInvoke ();
+
+               private static bool arg_type_match (Type delArgType, Type argType) {
+                       bool match = delArgType == argType;
+
+                       // Delegate contravariance
+                       if (!match) {
+                               if (!argType.IsValueType && argType.IsAssignableFrom (delArgType))
+                                       match = true;
+                       }
+
+                       return match;
+               }
+
+               private static bool return_type_match (Type delReturnType, Type returnType) {
+                       bool returnMatch = returnType == delReturnType;
+
+                       if (!returnMatch) {
+                               // Delegate covariance
+                               if (!returnType.IsValueType && delReturnType.IsAssignableFrom (returnType))
+                                       returnMatch = true;
+                       }
+
+                       return returnMatch;
+               }
 
-               public static Delegate CreateDelegate (Type type, MethodInfo method)
+               public static Delegate CreateDelegate (Type type, object firstArgument, MethodInfo method, bool throwOnBindFailure)
                {
+                       // The name of the parameter changed in 2.0
+                       object target = firstArgument;
+
                        if (type == null)
                                throw new ArgumentNullException ("type");
 
@@ -109,94 +162,122 @@ namespace System
                        if (!type.IsSubclassOf (typeof (MulticastDelegate)))
                                throw new ArgumentException ("type is not a subclass of Multicastdelegate");
 
-                       if (!method.IsStatic)
-                               throw new ArgumentException ("The method should be static.", "method");
-
                        MethodInfo invoke = type.GetMethod ("Invoke");
 
-                       // FIXME: Check the return type on the 1.0 profile as well
-#if NET_2_0
-                       Type returnType = method.ReturnType;
-                       Type delReturnType = invoke.ReturnType;
-                       bool returnMatch = returnType == delReturnType;
-
-                       if (!returnMatch) {
-                               // Delegate covariance
-                               if (!delReturnType.IsValueType && (delReturnType != typeof (ValueType)) && (delReturnType.IsAssignableFrom (returnType)))
-                                       returnMatch = true;
-                       }
-
-                       if (!returnMatch)
-                               throw new ArgumentException ("method return type is incompatible");
-#endif
+                       if (!return_type_match (invoke.ReturnType, method.ReturnType))
+                               if (throwOnBindFailure)
+                                       throw new ArgumentException ("method return type is incompatible");
+                               else
+                                       return null;
 
                        ParameterInfo[] delargs = invoke.GetParameters ();
                        ParameterInfo[] args = method.GetParameters ();
 
-                       if (args.Length != delargs.Length)
-                               throw new ArgumentException ("method argument length mismatch");
-                       
-                       int length = delargs.Length;
-                       for (int i = 0; i < length; i++) {
-                               bool match = delargs [i].ParameterType == args [i].ParameterType;
-
-#if NET_2_0
-                               // Delegate contravariance
-                               if (!match) {
-                                       Type argType = delargs [i].ParameterType;
+                       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);
+                               else {
+                                       argLengthMatch = (args.Length == delargs.Length);
+
+                                       if (!argLengthMatch)
+                                               // closed over a null reference
+                                               argLengthMatch = args.Length == delargs.Length + 1;
+                               }
+                       }
+                       if (!argLengthMatch)
+                               if (throwOnBindFailure)
+                                       throw new ArgumentException ("method argument length mismatch");
+                               else
+                                       return null;
 
-                                       if (!argType.IsValueType && (argType != typeof (ValueType)) && (args [i].ParameterType.IsAssignableFrom (argType)))
-                                               match = true;
+                       bool argsMatch;
+                       if (target != null) {
+                               if (!method.IsStatic) {
+                                       argsMatch = arg_type_match (target.GetType (), method.DeclaringType);
+                                       for (int i = 0; i < args.Length; i++)
+                                               argsMatch &= arg_type_match (delargs [i].ParameterType, args [i].ParameterType);
+                               } else {
+                                       argsMatch = arg_type_match (target.GetType (), args [0].ParameterType);
+                                       for (int i = 1; i < args.Length; i++)
+                                               argsMatch &= arg_type_match (delargs [i - 1].ParameterType, args [i].ParameterType);                                    
+                               }
+                       } else {
+                               if (!method.IsStatic) {
+                                       // The first argument should match this
+                                       argsMatch = arg_type_match (delargs [0].ParameterType, method.DeclaringType);
+                                       for (int i = 0; i < args.Length; i++)
+                                               argsMatch &= arg_type_match (delargs [i + 1].ParameterType, args [i].ParameterType);
+                               } else {
+                                       if (delargs.Length + 1 == args.Length) {
+                                               // closed over a null reference
+                                               argsMatch = !args [0].ParameterType.IsValueType;
+                                               for (int i = 0; i < delargs.Length; i++)
+                                                       argsMatch &= arg_type_match (delargs [i].ParameterType, args [i + 1].ParameterType);
+                                       } else {
+                                               argsMatch = true;
+                                               for (int i = 0; i < args.Length; i++)
+                                                       argsMatch &= arg_type_match (delargs [i].ParameterType, args [i].ParameterType);
+                                       }
                                }
-#endif
+                       }
 
-                               if (!match)
+                       if (!argsMatch)
+                               if (throwOnBindFailure)
                                        throw new ArgumentException ("method arguments are incompatible");
-                       }
+                               else
+                                       return null;
 
-                       return CreateDelegate_internal (type, null, method);
+                       Delegate d = CreateDelegate_internal (type, target, method, throwOnBindFailure);
+                       if (d != null)
+                               d.original_method_info = method;
+                       return d;
                }
 
-#if NET_2_0
-               public
-#else
-               internal
-#endif
-               static Delegate CreateDelegate (Type type, object target, MethodInfo method)
-               {
-                       if (type == null)
-                               throw new ArgumentNullException ("type");
-
-                       if (method == null)
-                               throw new ArgumentNullException ("method");
-
-                       if (!type.IsSubclassOf (typeof (MulticastDelegate)))
-                               throw new ArgumentException ("type is not a subclass of Multicastdelegate");
+               public static Delegate CreateDelegate (Type type, object firstArgument, MethodInfo method) {
+                       return CreateDelegate (type, firstArgument, method, true);
+               }
 
-                       return CreateDelegate_internal (type, target, method);
+               public static Delegate CreateDelegate (Type type, MethodInfo method, bool throwOnBindFailure)
+               {
+                       return CreateDelegate (type, null, method, throwOnBindFailure);
+               }
 
+               public static Delegate CreateDelegate (Type type, MethodInfo method) {
+                       return CreateDelegate (type, method, true);
                }
-               
+
                public static Delegate CreateDelegate (Type type, object target, string method)
                {
                        return CreateDelegate(type, target, method, false);
                }
 
-               public static Delegate CreateDelegate (Type type, Type target, string method)
+               static MethodInfo GetCandidateMethod (Type type, Type target, string method, BindingFlags bflags, bool ignoreCase, bool throwOnBindFailure)
                {
                        if (type == null)
                                throw new ArgumentNullException ("type");
 
-                       if (target == null)
-                               throw new ArgumentNullException ("target");
-
                        if (method == null)
                                throw new ArgumentNullException ("method");
 
                        if (!type.IsSubclassOf (typeof (MulticastDelegate)))
                                throw new ArgumentException ("type is not subclass of MulticastDelegate.");
 
-                       ParameterInfo[] delargs = type.GetMethod ("Invoke").GetParameters ();
+                       MethodInfo invoke = type.GetMethod ("Invoke");
+                       ParameterInfo [] delargs = invoke.GetParameters ();
                        Type[] delargtypes = new Type [delargs.Length];
 
                        for (int i=0; i<delargs.Length; i++)
@@ -206,53 +287,79 @@ namespace System
                         * FIXME: we should check the caller has reflection permission
                         * or if it lives in the same assembly...
                         */
-                       BindingFlags flags = BindingFlags.ExactBinding | BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic;
-                       MethodInfo info = target.GetMethod (method, flags, null, delargtypes, new ParameterModifier [0]);
 
-                       if (info == null)
-                               throw new ArgumentException ("Couldn't bind to method.");
+                       /*
+                        * 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;
 
-                       return CreateDelegate_internal (type, null, info);
+                       if (ignoreCase)
+                               flags |= BindingFlags.IgnoreCase;
+
+                       MethodInfo info = null;
+
+                       for (Type targetType = target; targetType != null; targetType = targetType.BaseType) {
+                               MethodInfo mi = targetType.GetMethod (method, flags,
+                                       null, delargtypes, new ParameterModifier [0]);
+                               if (mi != null && return_type_match (invoke.ReturnType, mi.ReturnType)) {
+                                       info = mi;
+                                       break;
+                               }
+                       }
+
+                       if (info == null) {
+                               if (throwOnBindFailure)
+                                       throw new ArgumentException ("Couldn't bind to method '" + method + "'.");
+                               else
+                                       return null;
+                       }
+
+                       return info;
                }
 
-               public static Delegate CreateDelegate (Type type, object target, string method, bool ignoreCase)
+               public static Delegate CreateDelegate (Type type, Type target, string method, bool ignoreCase, bool throwOnBindFailure)
                {
-                       if (type == null)
-                               throw new ArgumentNullException ("type");
-
                        if (target == null)
                                throw new ArgumentNullException ("target");
 
-                       if (method == null)
-                               throw new ArgumentNullException ("method");
-
-                       if (!type.IsSubclassOf (typeof (MulticastDelegate)))
-                               throw new ArgumentException ("type");
-
-                       ParameterInfo[] delargs = type.GetMethod ("Invoke").GetParameters ();
-                       Type[] delargtypes = new Type [delargs.Length];
+                       MethodInfo info = GetCandidateMethod (type, target, method,
+                               BindingFlags.Static, ignoreCase, throwOnBindFailure);
+                       if (info == null)
+                               return null;
 
-                       for (int i=0; i<delargs.Length; i++)
-                               delargtypes [i] = delargs [i].ParameterType;
+                       return CreateDelegate_internal (type, null, info, throwOnBindFailure);
+               }
 
-                       /* 
-                        * FIXME: we should check the caller has reflection permission
-                        * or if it lives in the same assembly...
-                        */
-                       BindingFlags flags = BindingFlags.ExactBinding | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
+               public static Delegate CreateDelegate (Type type, Type target, string method) {
+                       return CreateDelegate (type, target, method, false, true);
+               }
 
-                       if (ignoreCase)
-                               flags |= BindingFlags.IgnoreCase;
+               public static Delegate CreateDelegate (Type type, Type target, string method, bool ignoreCase) {
+                       return CreateDelegate (type, target, method, ignoreCase, true);
+               }
 
-                       MethodInfo info = target.GetType ().GetMethod (method, flags, null, delargtypes, new ParameterModifier [0]);
+               public static Delegate CreateDelegate (Type type, object target, string method, bool ignoreCase, bool throwOnBindFailure)
+               {
+                       if (target == null)
+                               throw new ArgumentNullException ("target");
 
+                       MethodInfo info = GetCandidateMethod (type, target.GetType (), method,
+                               BindingFlags.Instance, ignoreCase, throwOnBindFailure);
                        if (info == null)
-                               throw new ArgumentException ("Couldn't bind to method '" + method + "'.");
+                               return null;
 
-                       return CreateDelegate_internal (type, target, info);
+                       return CreateDelegate_internal (type, target, info, throwOnBindFailure);
                }
 
-               public object DynamicInvoke (object[] args)
+               public static Delegate CreateDelegate (Type type, object target, string method, bool ignoreCase) {
+                       return CreateDelegate (type, target, method, ignoreCase, true);
+               }
+
+               public object DynamicInvoke (params object[] args)
                {
                        return DynamicInvokeImpl (args);
                }
@@ -264,10 +371,9 @@ namespace System
                                for (int i = 0; i < args.Length; ++i) {
                                        mtypes [i] = args [i].GetType ();
                                }
-                               method_info = m_target.GetType ().GetMethod (method_name, mtypes);
+                               method_info = m_target.GetType ().GetMethod (data.method_name, mtypes);
                        }
 
-#if NET_2_0
                        if ((m_target != null) && Method.IsStatic) {
                                // The delegate is bound to m_target
                                if (args != null) {
@@ -280,7 +386,6 @@ namespace System
                                }
                                return Method.Invoke (null, args);
                        }
-#endif
 
                        return Method.Invoke (m_target, args);
                }
@@ -298,17 +403,23 @@ namespace System
                                return false;
                        
                        // Do not compare method_ptr, since it can point to a trampoline
-                       if ((d.target_type == target_type) && (d.m_target == m_target) &&
-                               (d.method_name == method_name) && (d.method_info == method_info))
+                       if (d.m_target == m_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
+                                               return false;
+                               }
                                return true;
+                       }
 
                        return false;
                }
 
                public override int GetHashCode ()
                {
-                       // FIXME: Sync with Equals above
-                       return (int)method_ptr;
+                       return method.GetHashCode () ^ (m_target != null ? m_target.GetHashCode () : 0);
                }
 
                protected virtual MethodInfo GetMethodImpl ()
@@ -351,7 +462,8 @@ namespace System
                ///   Returns a new MulticastDelegate holding the
                ///   concatenated invocation lists of an Array of MulticastDelegates
                /// </symmary>
-               public static Delegate Combine (Delegate[] delegates)
+               [System.Runtime.InteropServices.ComVisible (true)]
+               public static Delegate Combine (params Delegate[] delegates)
                {
                        if (delegates == null)
                                return null;
@@ -366,7 +478,7 @@ namespace System
 
                protected virtual Delegate CombineImpl (Delegate d)
                {
-                       throw new MulticastNotSupportedException ("");
+                       throw new MulticastNotSupportedException (String.Empty);
                }
 
                public static Delegate Remove (Delegate source, Delegate value) 
@@ -384,7 +496,7 @@ namespace System
 
                        return this;
                }
-#if NET_1_1
+
                public static Delegate RemoveAll (Delegate source, Delegate value)
                {
                        Delegate tmp = source;
@@ -393,22 +505,27 @@ namespace System
 
                        return tmp;
                }
-#endif
-               public static bool operator == (Delegate a, Delegate b)
+
+               public static bool operator == (Delegate d1, Delegate d2)
                {
-                       if ((object)a == null) {
-                               if ((object)b == null)
+                       if ((object)d1 == null) {
+                               if ((object)d2 == null)
                                        return true;
                                return false;
-                       } else if ((object) b == null)
+                       } else if ((object) d2 == null)
                                return false;
                        
-                       return a.Equals (b);
+                       return d1.Equals (d2);
+               }
+
+               public static bool operator != (Delegate d1, Delegate d2)
+               {
+                       return !(d1 == d2);
                }
 
-               public static bool operator != (Delegate a, Delegate b)
+               internal bool IsTransparentProxy ()
                {
-                       return !(a == b);
+                       return RemotingServices.IsTransparentProxy (m_target);
                }
        }
 }