title: Questions about the way out, ref, and [Out] behave in unmanaged delegate signatures
body: <p>I happen to use heavy Interoperability features in some of my C# programs. In this process, I have found some odd behavior and limits to the out, ref, and [Out] options that exist. I also have found some work around to some of the limitations.</p>
  
  <p>Given this, I would like to ask about why the examples given below behave the way they do. It would be nice to not have to use such tricks to work-around the limitation of using out or ref in params object[] signatures (or any params, actually). I am sure it is more likely than not there is good enough reason to not allow it even though the given work around's exist as shown below.</p>
  
  <p>I will try to illustrate the behavior I refer to in the pseudo code below, comments explain most of what goes on. Feel free to ask me more specifics.</p>
  
  <p>Incase it is not clear what my question is: Why am I not allowed to use out/ref tags in my parameters for my method, despite it being totally possible and working just fine if the "tricks" below are applied. </p>
  
  <pre><code>class InteropExamples {
      /* note: these delegates fields must be kept alive in a real class too,
         or else GC can collect them.
      */
  
      // One method has out, the other [Out] attribute (in order, A out, B [Out])
      readonly TryGetLocationA _tryGetLocationA;
      readonly TryGetLocationB _tryGetLocationB;
  
      // One method has ref IntPtr, the other IntPtr[] (in order, A ref IntPtr, B IntPtr[])
      readonly IsEntityValidA _isEntityValidA;
      readonly IsEntityValidB _isEntityValidB;
  
      readonly int _mainThreadID;
  
      public bool InvokeRequired =&gt; GetCurrentThreadId() != _mainThreadID;
  
      public InteropExamples() {
          var addressOne = IntPtr.Zero; // real address of the func here of course.
          var addressTwo = IntPtr.Zero; // real address of the func here of course.
  
          _tryGetLocationA =
              Marshal.GetDelegateForFunctionPointer(addressOne, typeof(TryGetLocationA)) as TryGetLocationA;
          _tryGetLocationB =
              Marshal.GetDelegateForFunctionPointer(addressOne, typeof(TryGetLocationB)) as TryGetLocationB;
  
          _isEntityValidA =
              Marshal.GetDelegateForFunctionPointer(addressTwo, typeof(IsEntityValidA)) as IsEntityValidA;
          _isEntityValidB =
              Marshal.GetDelegateForFunctionPointer(addressTwo, typeof(IsEntityValidB)) as IsEntityValidB;
  
          _mainThreadID = System.Diagnostics.Process.GetCurrentProcess().Threads[0].Id;
      }
  
  
      // This way works as expected with no limits.
      // delegate void TryGetLocationA(IntPtr address, out Vector3 result);   
      public Vector3 GetLocationA(IntPtr address) {
          Vector3 location;
          // note: Invoke(_tryGetLocationA, address, out location) is not possible here.
          _tryGetLocationA(address, out location);
          return location;
      }
  
      // In this example, we use a trick to be able to 
      // use the out despite using params object[] args
      // not allowing you to pass out or ref.
      // delegate void TryGetLocationB(IntPtr address, [Out] Vector3[] result);   
      public Vector3 GetLocationB(IntPtr address) {
          // note: if your delegate signature - 
          // has out and not the attribute [Out]
          // it will fail.
          var location = new Vector3[1];
          // The result gets passed to the first index of the array, 0.
          // Use our special invoke method with params object[] thus not allowing - 
          // us to use out directly like in example A.
          Invoke(_tryGetLocationB, address, location);
          return location[0];
      }
  
      // This way works fine and as expected.
      public bool IsEntityValid_A(IntPtr entityAddress) {
          // note:  (bool) Invoke(_isEntityValidA, ref entityAddress) is not valid here.
          return _isEntityValidA(ref entityAddress);
      }
  
      public bool IsEntityValid_B(IntPtr entityAddress) {
          // note: no ref tag needed here, unlike the [Out] example.
          // The array trick allows us to once again by pass the limit -
          // of not being able to use ref in params object[] (or params of any kind).
          var isEntityValid = (bool) Invoke(_isEntityValidB, new[] {entityAddress});
          return isEntityValid;
      }
  
  
      // An example of why we might want to invoke unmanaged funcs with -
      // a generic method using params[] object[] args, which sadly -
      // with out using tricks will not allow use of out/ref signatures.
      // Work arounds exist and shown in this class.
      object Invoke&lt;T&gt;(T target, params object[] args) where T : class {
          var targetDelegate = target as Delegate;
          if (targetDelegate == null) {
              throw new ArgumentException("Target method is not a delegate type.");
          }
  
          if (!InvokeRequired) {
              return targetDelegate.DynamicInvoke(args);
          }
  
          var callback = new InvokeDelegateTarget {
              Target = targetDelegate,
              Args = args
          };
  
          /* notify some class to invoke your delegate */
          Helper.ProcessDelegate(callback);
          /* while callback.Result == null {} or what ever check you do */
  
          return callback.Result;
      }
  
      [DllImport("kernel32.dll")]
      static extern int GetCurrentThreadId();
  
      [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
      delegate void TryGetLocationB(IntPtr address, [Out] Vector3[] result);
  
      [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
      delegate void TryGetLocationA(IntPtr address, out Vector3 result);
  
      [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
      delegate bool IsEntityValidA(ref IntPtr entityAddress);
  
  
      [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
      delegate bool IsEntityValidB(IntPtr[] entityAddress);
  }
  </code></pre>