13. Custom Attributes and Reflection
CLI languages use metadata inspection and the System.Reflection
libraries to make guarantees
about how compiled entities appear at runtime. They also allow entities to be attributed by static
data, and these attributes may be accessed and read by tools and running programs. This chapter
describes these mechanisms for F#.
Attributes are given by the following grammar:
attribute := attribute-target : opt object-construction
attribute-set := [< attribute ; ... ; attribute >]
attributes := attribute-set ... attribute-set
attribute-target :=
assembly
module
return
field
property
param
type
constructor
event
13.1 Custom Attributes
CLI languages support the notion of custom attributes which can be added to most declarations. These are added to the corresponding elaborated and compiled forms of the constructs to which they apply.
Custom attributes can be applied only to certain target language constructs according to the
AttributeUsage
attribute, which is found on the attribute class itself. An error occurs if an attribute is
attached to a language construct that does not allow that attribute.
Custom attributes are not permitted on function or value definitions in expressions or computation expressions. Attributes on parameters are given as follows:
let foo([<SomeAttribute>] a) = a + 5
If present, the arguments to a custom attribute must be literal constant expressions , or arrays of the same.
Custom attributes on return values are given as follows:
let foo a : [<SomeAttribute>] = a + 5
Custom attributes on primary constructors are given before the arguments and before any accessibility annotation:
type Foo1 [<System.Obsolete("don't use me")>] () =
member x.Bar() = 1
type Foo2 [<System.Obsolete("don't use me")>] private () =
member x.Bar() = 1
Custom attributes are mapped to compiled CLI metadata as follows:
- Custom attributes map to the element that is specified by their target, if a target is given.
- A custom attribute on a type
type
is compiled to a custom attribute on the corresponding CLI type definition, whoseSystem.Type
object is returned bytypeof<type>
. - By default, a custom attribute on a record field
F
for a typeT
is compiled to a custom attribute on the CLI property for the field that is namedF
, unless the target of the attribute isfield
, in which case it becomes a custom attribute on the underlying backing field for the CLI property that is named_F
. - A custom attribute on a union case
ABC
for a typeT
is compiled to a custom attribute on a static method on the CLI type definitionT
. This method is called: get_ABC
if the union case takes no argumentsABC
otherwise- Custom attributes on arguments are propagated only for arguments of member definitions, and not for “let”-bound function definitions.
- Custom attributes on generic parameters are not propagated.
Custom attributes that appear immediately preceding “do” statements in modules anywhere in an assembly are attached to one of the following:
- The
main
entry point of the program. - The compiled module.
- The compiled assembly.
Custom attributes are attached to the main entry point if it is valid for them to be attached to a
method according to the AttributeUsage
attribute that is found on the attribute class itself, and
likewise for the assembly. If it is valid for the attribute to be attached to either the main method or
the assembly. the main method takes precedence.
For example, the STAThread
attribute should be placed immediately before a top-level do
statement.
let main() =
let form = new System.Windows.Forms.Form()
System.Windows.Forms.Application.Run(form)
[<STAThread>]
do main()
13.1.1 Custom Attributes and Signatures
During signature checking, custom attributes attached to items in F# signature files (.fsi
files) are
combined with custom attributes on the corresponding element from the implementation file
according to the following algorithm:
- Start with lists
AImpl
andASig
containing the attributes in the implementation and signature, in declaration order. - Check each attribute in
AImpl
against the available attributes inASig
. - If
ASig
contains an attribute that is an exact match after evaluating attribute arguments, then ignore the attribute in the implementation, remove the attribute fromASig
, and continue checking; - If
ASig
contains an attribute that has the same attribute type but is not an exact match, then give a warning and ignore the attribute in the implementation; - Otherwise, keep the attribute in the implementation.
The compiled element contains the compiled forms of the attributes from the signature and the retained attributes from the implementation.
This means:
- When an implementation has an attribute
X("abc")
and the signature is missing the attribute, then no warning is given and the attribute appears in the compiled assembly. - When a signature has an attribute
X("abc")
and the implementation is missing the attribute, then no warning is given, and the attribute appears in the compiled assembly. - When an implementation has an attribute
X("abc")
and the signature has attributeX("def")
, then a warning is given, and onlyX("def")
appears in the compiled assembly.
13.2 Reflected Forms of Declaration Elements
The typeof
and typedefof
F# library operators return a System.Type
object for an F# type definition.
According to typical implementations of the CLI execution environment, the System.Type
object in
turn can be used to access further information about the compiled form of F# member declarations.
If this operation is supported in a particular implementation of F#, then the following rules describe
which declaration elements have corresponding System.Reflection
objects:
- All member declarations are present as corresponding methods, properties or events.
- Private and internal members and types are included.
- Type abbreviations are not given corresponding
System.Type
definitions.
In addition:
- F# modules are compiled to provide a corresponding compiled CLI type declaration and
System.Type
object, although theSystem.Type
object is not accessible by using thetypeof
operator.
However:
- Internal and private function and value definitions are not guaranteed to be given corresponding compiled CLI metadata definitions. They may be removed by optimization.
- Additional internal and private compiled type and member definitions may be present in the compiled CLI assembly as necessary for the correct implementation of F# programs.
- The
System.Reflection
operations return results that are consistent with the erasure of F# type abbreviations and F# unit-of-measure annotations. - The definition of new units of measure results in corresponding compiled CLI type declarations
with an associated
System.Type
.