Pseudo custom attributes

Tomorrow I'd like to talk about the PreserveSig pseudo custom attribute, but first I thought I'd briefly point out the difference between pseudo custom attributes and "real" custom attributes.

The CLR/.NET Framework has a handful of pseudo custom attributes that are used just like real custom attributes in high-level source code, but are not stored in metadata the same way that custom attributes are.  Each pseudo custom attribute is represented with unique bits in metadata, which happens to be more efficient than a general-purpose custom attribute.

Ideally, you wouldn't have to know whether a custom attribute is a pseudo custom attribute unless you were doing low-level metadata processing.  Unfortunately, the illusion that pseudo custom attributes are just like real custom attributes breaks down when you use reflection (at least in versions 1.0 and 1.1 of the .NET Framework).  The GetCustomAttributes APIs do not "see" pseudo custom attributes, although other APIs can be used to determine whether specific pseudo custom attributes are present.

The following C# class is marked with a pseudo custom attribute (SerializableAttribute) and a real custom attribute (ObsoleteAttribute):

  using System;

  [Serializable, Obsolete]

  public class C

  {

    ...

  }

We can see the difference between the two by viewing the compiled class using the IL Disassembler (ILDASM.EXE):

  .class public auto ansi serializable

    beforefieldinit C

  extends [mscorlib]System.Object

  {

  .custom instance void

      [mscorlib]System.ObsoleteAttribute::.ctor()

      = ( 01 00 00 00 )

  ...

  } // end of class C

If you use reflection to get the custom attributes on C:

  Type t = typeof(C);

  object [] attributes = t.GetCustomAttributes(false);

  foreach (object o in attributes)

    Console.WriteLine(o);

you get only the single real custom attribute returned:

  System.ObsoleteAttribute

But you could find out that C is marked with SerializableAttribute by checking the value of the Type.IsSerializable property.  For some pseudo custom attributes, like MarshalAsAttribute, there simply is no way to get all the attribute's information without using the metadata APIs.

Reflection emit hides the differences between pseudo custom attributes and real custom attributes by letting you use the attribute classes when emitting either kind of attribute.  However, pseudo custom attributes tend to get special treatment in the reflection emit APIs.  For example, TypeBuilder and ModuleBuilder have a DefinePInvokeMethod API that emits the DllImportAttribute bits for you (although it's not general enough to express everything that the attribute can).

Unlike custom attributes, pseudo custom attributes are not used for extensibility - there's a predefined list:

System.NonSerializedAttribute
System.SerializableAttribute
System.Runtime.CompilerServices.MethodImplAttribute
System.Runtime.InteropServices.ComImportAttribute
System.Runtime.InteropServices.DllImportAttribute
System.Runtime.InteropServices.FieldOffsetAttribute
System.Runtime.InteropServices.InAttribute
System.Runtime.InteropServices.MarshalAsAttribute
System.Runtime.InteropServices.OptionalAttribute
System.Runtime.InteropServices.OutAttribute
System.Runtime.InteropServices.PreserveSigAttribute
System.Runtime.InteropServices.StructLayoutAttribute

The following attributes technically aren't pseudo custom attributes from the CLR perspective, but exhibit the same behavior:

System.Reflection.AssemblyAlgorithmIdAttribute
System.Reflection.AssemblyCultureAttribute
System.Reflection.AssemblyFlagsAttribute
System.Reflection.AssemblyVersionAttribute

That's because compilers like Visual C# and VB.NET emit specific metadata when these are used, rather than emitting the custom attributes themselves.  (I guess that makes them pseudo pseudo custom attributes?)

There are also security attributes, such as the attributes in the System.Security.Permissions namespace, which are serialized into metadata as XML.  Although these are not pseudo custom attributes, they can't be retrieved via reflection due to their distinct metadata representation.

Finally, I want to share an interesting fact about the IL Assembler (ILASM.EXE) that I didn't know until recently.  Whenever I've written pseudo custom attributes in IL Assembler, I've used the syntax that ILDASM produces.  But like C#, VB.NET, and other .NET languages, ILASM allows you to use regular custom attribute syntax with pseudo custom attributes!  If you compile the following with ILASM:

  .class public auto ansi beforefieldinit C

  extends [mscorlib]System.Object

  {

  .custom instance void

      [mscorlib]System.ObsoleteAttribute::.ctor()

      = ( 01 00 00 00 )

    .custom instance void [mscorlib]

      System.SerializableAttribute::.ctor()

      = ( 01 00 00 00 )

  ...

  } // end of class C

then view the result with ILDASM, you'll see the same metadata shown earlier when disassembling the C# code.  The SerializableAttribute, when compiled, becomes the serializable bit in metadata.