Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 773
g t Avatar asked Jan 30 '12 22:01

g t


People also ask

How do attributes work in C#?

In C#, attributes are classes that inherit from the Attribute base class. Any class that inherits from Attribute can be used as a sort of "tag" on other pieces of code. For instance, there is an attribute called ObsoleteAttribute . This is used to signal that code is obsolete and shouldn't be used anymore.

Do you need to declare an out variable before you use it?

Variables passed as out arguments do not have to be initialized before being passed in a method call. However, the called method is required to assign a value before the method returns.

Why attributes are used in C#?

Attributes are used in C# to convey declarative information or metadata about various code elements such as methods, assemblies, properties, types, etc. Attributes are added to the code by using a declarative tag that is placed using square brackets ([ ]) on top of the required code element.


2 Answers

To note, there has been additional documentation added since the original accepted answer was posted.

The definition of the directional attributes is:

  • [In]: Data modified in the unmanaged code is not written back to managed code. All data passed by value is treated as [In] by default.
  • [In/Out]: Data modified in the unmanaged code writes back to managed code. Data passed by reference (including by using the ref keyword) are treated as [In/Out] by default.
  • [Out]: Parameters modified by the out C# keyword must as expected be passed back - and default to [Out]. Using [Out] explicitly will behave identically to [In/Out] ([In] is always assumed).

So the key uses of explicitly modifying these are:

  • To instruct the program not to pass back modified data passed by reference - by using [In]
  • To instruct the program to return modified data passed to the unmanaged code - by using [In/Out] or simply [Out] (they are equivalent).

The latter of these is particularly useful for reference types which pass by value by default, such as arrays of value types - e.g. byte[].

like image 153
Haighstrom Avatar answered Oct 04 '22 18:10

Haighstrom


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;
    }
}
like image 36
Hans Passant Avatar answered Oct 04 '22 18:10

Hans Passant