How do the In and Out attributes work in .NET?

2019-01-07 21:36发布

问题:

I have been trying to serialize an array across an AppDomain boundary, using the following code:

public int Read(byte[] buffer, int offset, int count)
{
    return base.Read(buffer, offset, count);
}

As a guess, after noticing the attributes elsewhere, I marked the method's parameters with [In] and [Out] attributes, which seemed to cause the parameters to behave as if they were passed by reference.

For example:

public int Read([In, Out] byte[] buffer, int offset, int count)
{
    return base.Read(buffer, offset, count);
}

Before I added the attributes, the contents of the buffer variable were lost after returning from the method across an AppDomain boundary.

The class (SslStream) was inheriting from MarshalByRefObject but not marked with the Serializable attribute. Is this the only way to make a parameter pass-by-value? Are these attributes being recognised somehow by .NET when the class is being serialised? And do they truly cause the parameter to be passed by reference, or are the contents just copied?

回答1:

This is a remarkably poorly documented feature of .NET Remoting. It doesn't have anything to do with whether your class is [Serializable] or derived from MarshalByRefObject. At issue here is how the argument is marshaled across the AppDomain boundary. The call itself is made under the hood by Remoting. Arrays do not automatically get marshaled back after the call, clearly a performance optimization. Only the [Out] attribute is required, [In] is implied. I could not find any relevant documentation about this in MSDN, just a blog post from somebody that ran into the same issue (scroll down to "Using OutAttribute in Remoting").

Some code to play with:

using System;
using System.Runtime.InteropServices;

class Program {
    static void Main(string[] args) {
        var ad = AppDomain.CreateDomain("second");
        var t = (Test)ad.CreateInstanceAndUnwrap(typeof(Test).Assembly.FullName, typeof(Test).FullName);
        var b = new byte[] { 1 };
        t.Read(b);
        System.Diagnostics.Debug.Assert(b[0] == 2);
    }
}

class Test : MarshalByRefObject {
    public void Read([Out]byte[] arg) {
        arg[0] *= 2;
    }
}