Attributes

Writing attributes on declarations, declaring your own with attribute, their constant arguments, the attributes the compiler and the library give meaning to, and reading attributes with reflection.

Last updated 6 min read Edit on GitHub

On this page

An attribute is a note on a declaration, written in brackets before it: [Column("name", Width = 40)]. The compiler reads some attributes itself; the others are kept with the program, and reflection gives them back as objects at run time.

Writing attributes

Antril
[Serializable]
[Column("people", Width = 40)]
[Tag("a"), Tag("b")]
public class Person { … }
  • Each attribute names a class derived from System.Attribute, written without its Attribute suffix: [Column] is the class ColumnAttribute. The full name works too.
  • Several attributes go in one pair of brackets, separated by commas, or each in its own.
  • The arguments in parentheses go to a constructor of the class. After them, Name = value sets a public field or a settable property.
  • Without arguments the parentheses can be left out: [Serializable].

Attributes can be written on:

Declaration Example
Types: classes, values, records, interfaces, enums [Tag("t")] public class C { … }
Enum members enum E { [Tag("a")] A, B }
Fields [Tag("f")] i32 F;
Properties [Tag("p")] i32 P { get; set; }
Constructors [Tag("c")] new() { }
Methods and free functions [Tag("m")] M() { }
Parameters M([Tag("x")] i32 x) { }

Warning

Not yet available: attributes on a function's result ([return: NotNull]).

Constant arguments

Every argument is known when the program compiles:

  • numbers, bool, characters and strings, and constants made of them (N * 2 for a const i32 N);
  • enum members;
  • typeof(T), for a parameter or field of type Type;
  • null, for a nullable parameter or field.
Code When
AN1058 The name is not an attribute: no class Name or NameAttribute derived from System.Attribute.
AN1059 The attribute class is abstract (a base class).
AN1060 No constructor of the class takes these arguments.
AN1061 Name = value names no public field or settable property.
AN1062 The value given to Name = value is not a constant of its type.

Declaring attributes

attribute Name { … } declares the class NameAttribute, derived from System.Attribute. The suffix is added when it is not written, and is not repeated when it is. Its base can be another attribute, written without the suffix:

AntrilRun
import System;

public attribute Column
{
	public:
		str Name;
		i32 Width;
		bool Required;

		new(str name) { Name = name; }
}

public attribute Note : Column
{
	public:
		new() : base("note") { }
}

[Column("people", Width = 40)]
public class Person
{
	public:
		[Column("full name", Required = true)] str Name = "";
		[Note] i32 Age;
}

Main()
{
}

An attribute class is a class like any other: fields, properties, constructors and methods. The base of an attribute must derive from System.Attribute (AN1096). The constraint T : attribute asks a type argument to be an attribute class (see Generics).

Reading attributes

System.Reflection gives each declaration's attributes as objects, made with the arguments written:

Member On What it gives
GetCustomAttributes() Type, FieldInfo, PropertyInfo, MethodInfo, ConstructorInfo, ParameterInfo Every attribute, as an any[].
GetCustomAttributes(typeof(A)) the same Those of class A or a class derived from it.
IsDefined(typeof(A)) the same Whether there is one.
AntrilRun
import System;
import System.Reflection;

public attribute Column
{
	public:
		str Name;
		i32 Width;
		bool Required;

		new(str name) { Name = name; }
}

public attribute Note : Column
{
	public:
		new() : base("note") { }
}

[Column("people", Width = 40)]
public class Person
{
	public:
		[Column("full name", Required = true)] str Name = "";
		[Note] i32 Age;

		[Column("greet")]
		Greet([Column("who")] str other) : str => "hi " + other;
}

Main()
{
	Type t = typeof(Person);
	loop (any a in t.GetCustomAttributes())
	{
		if (a is ColumnAttribute c)
			Console.WriteLine("type: " + c.Name + " " + c.Width);
	}
	FieldInfo? name = t.GetField("Name");
	if (name is FieldInfo f && f.GetCustomAttributes(typeof(ColumnAttribute))[0] is ColumnAttribute col)
		Console.WriteLine("field: " + col.Name + " " + col.Required);
	if (t.GetField("Age") is FieldInfo age)
		Console.WriteLine("Age has Note: " + age.IsDefined(typeof(NoteAttribute)));
	MethodInfo? greet = t.GetMethod("Greet");
	if (greet is MethodInfo m)
	{
		ParameterInfo p = m.GetParameters()[0];
		if (p.GetCustomAttributes()[0] is ColumnAttribute pc)
			Console.WriteLine("parameter: " + pc.Name);
	}
	Console.WriteLine(t.IsDefined(typeof(NoteAttribute)));
}
Terminal
larva run
type: people 40
field: full name true
Age has Note: true
parameter: who
false

Enum members appear among their type's fields (GetFields()), with their attributes.

Attributes the compiler reads

These are read where they apply and are not kept for reflection. The compiler knows them by name, so they need no import.

Attribute On What it does
[Inline] functions and methods Asks the back end to inline every call.
[NoInline] functions and methods Asks the back end never to inline it.
[Import("lib")], [Import("lib", "Entry")] functions without a body A C function of a native library (see Interop).
[Export], [Export("name")] static functions Makes the function a C symbol (see Interop).
[Primitive("i32")] value types of the core library Says which primitive type a library type stands for (System.Int32 is i32). Only the core library uses it (AN1003, AN1004).

Attributes of the library

Attribute Namespace What it does
[FriendOf(T)] System Makes the type and T friends (both ways, from any library): each sees the other's friendly members. It names one type (AN1070), not the type itself (AN1071).
[ValueLayout(Layout.Sequential or Layout.Explicit, Pack = n, Size = n)] System.Runtime.InteropServices Where the fields of a value go in memory (see Interop).
[FieldOffset(n)] System.Runtime.InteropServices A field's offset in a Layout.Explicit value.
AntrilRun
import System;

[FriendOf(Vault)]
public class Auditor
{
	public:
		Peek(Vault v) : i32 => v.secret;
}

public class Vault
{
	friendly:
		i32 secret = 42;
}

Main()
{
	Console.WriteLine(new Auditor().Peek(new Vault()));
	Console.WriteLine(typeof(Vault).IsFriendOf(typeof(Auditor)));
}
Terminal
larva run
42
true

Another type that reads v.secret gets AN1401: `secret` is friendly: only for `Vault` and its friends. See Access.

Was this page helpful?

No address is stored; your user name is, if you are signed in.

Esc