Attributes, Caller Information and Reflection

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-20. The examples are complete console programs that start with Option Strict On; they were built with warnings treated as errors (except where the page says the opposite) and run in the .NET 10 SDK container (Linux, arm64). The CLR and compiler messages include the project’s root namespace, which is ShelfApp for every single-file example (the assembly is named App); the plugin example is a three-project solution. Where an example is meant to fail, the messages shown are the ones the SDK printed.

Three features share this page because they share one mechanism: metadata. The compiler writes information about your code into the assembly, and a running program can read it back. An attribute is metadata that you add; caller information is metadata the compiler fills in for you at each call; reflection is the API that reads it all, and that can also act on what it finds, by creating objects and calling members whose names it learned at run time. Calling members through an Object variable under Option Strict Off is a related but different mechanism, late binding, and has its own page: Early and Late Binding. The C# counterpart is on Attributes and Reflection (C#).

Attributes

An attribute is a class that derives from System.Attribute, applied to a declaration with angle brackets. It does nothing by itself: the compiler stores it in the assembly, and some other code (the compiler, a tool, the runtime, your program) reads it and acts on it. Visual Basic puts the attribute list before the declaration, inside < and >:

<Attribute1(positionalArgument, NamedProperty:=value)>
<Attribute2>
Public Class Example

The syntax is regular:

  • The class name may omit the Attribute suffix: <Obsolete> is ObsoleteAttribute.

  • Positional arguments come first and select a constructor. Named arguments follow and set a public field or property with := (C# uses =), in any order.

  • Parentheses are optional when there are no arguments (<Serializable>).

  • Several attributes can share one list, separated by commas (<Description("a"), Category("b")>), or be written as separate lists. A list may be broken over lines after a comma; a closing > may sit on the next line after a line-continuation underscore.

  • The list is part of the declaration, not a separate statement, so there is no attribute syntax for local variables or statements.

The program below applies attributes to every kind of target Visual Basic allows and then reads each one back by reflection. It exercises the target list in the next table.

Option Strict On

Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Reflection

<Assembly: AssemblyMetadata("Catalogue", "Shelf")>
<Module: Description("The catalogue module")>

<Description("A book in the catalogue"), DebuggerDisplay("{Title,nq} ({Year})")>
Public Class Book
    <DefaultValue(1990)>
    Public Property Year As Integer

    Public Property Title As String = ""

    <Obsolete("Use Title instead", False)>
    Public Property Name As String = ""

    <Description("Copies on the shelf")>
    Public CopiesOnShelf As Integer

    <Description("Raised when a copy is lent")>
    Public Event Borrowed As EventHandler

    <Description("Pages of one edition")>
    Public Function Pages(<Description("Edition number")> edition As Integer) As <Description("The number of pages")> Integer
        Return 100 * edition
    End Function
End Class

<Description("One loan")>
Public Structure Loan
    Public Days As Integer
End Structure

<Description("A shelf")>
Public Interface IShelf
End Interface

<Description("Called when a book changes")>
Public Delegate Sub BookChanged(title As String)

Public Enum Shelfmark
    <Description("Fiction")> Fiction = 1
    <Description("Reference")> Reference = 2
End Enum

Module Program
    Function Member(found As MemberInfo) As MemberInfo
        If found Is Nothing Then Throw New InvalidOperationException("Member not found")
        Return found
    End Function

    Sub Show(label As String, attributes As IEnumerable(Of Attribute))
        For Each attribute As Attribute In attributes
            Console.WriteLine(label.PadRight(22) & attribute.GetType().Name)
        Next
    End Sub

    Sub Main()
        Dim bookType As Type = GetType(Book)
        Show("class", bookType.GetCustomAttributes(False).Cast(Of Attribute)())
        Show("property Year", Member(bookType.GetProperty("Year")).GetCustomAttributes(False).Cast(Of Attribute)())
        Show("property Name", Member(bookType.GetProperty("Name")).GetCustomAttributes(False).Cast(Of Attribute)())
        Show("event", Member(bookType.GetEvent("Borrowed")).GetCustomAttributes(False).Cast(Of Attribute)())
        Show("structure", GetType(Loan).GetCustomAttributes(False).Cast(Of Attribute)())
        Show("interface", GetType(IShelf).GetCustomAttributes(False).Cast(Of Attribute)())
        Show("delegate", GetType(BookChanged).GetCustomAttributes(False).Cast(Of Attribute)())
        Show("field", Member(bookType.GetField("CopiesOnShelf")).GetCustomAttributes(False).Cast(Of Attribute)())
        Dim pages As MethodInfo = CType(Member(bookType.GetMethod("Pages")), MethodInfo)
        Show("method", pages.GetCustomAttributes(False).Cast(Of Attribute)())
        Show("return value", pages.ReturnTypeCustomAttributes.GetCustomAttributes(False).Cast(Of Attribute)())
        Show("parameter", pages.GetParameters()(0).GetCustomAttributes(False).Cast(Of Attribute)())
        Show("enum member", Member(GetType(Shelfmark).GetField("Fiction")).GetCustomAttributes(False).Cast(Of Attribute)())
        Show("assembly", Assembly.GetExecutingAssembly().GetCustomAttributes(GetType(AssemblyMetadataAttribute)).Cast(Of Attribute)())
        Show("module", bookType.Module.GetCustomAttributes(False).Cast(Of Attribute)())
    End Sub
End Module
class                 DescriptionAttribute
class                 DebuggerDisplayAttribute
property Year         DefaultValueAttribute
property Name         ObsoleteAttribute
event                 DescriptionAttribute
structure             DescriptionAttribute
interface             DescriptionAttribute
delegate              DescriptionAttribute
field                 DescriptionAttribute
method                DescriptionAttribute
return value          DescriptionAttribute
parameter             DescriptionAttribute
enum member           DescriptionAttribute
assembly              AssemblyMetadataAttribute
module                DescriptionAttribute
Target How to write it in Visual Basic Seen in the program

Class, structure, interface, delegate, enum

the list before the declaration

Description and DebuggerDisplay on Book; Description on Loan, IShelf and BookChanged

Method (Sub or Function)

before the declaration

Description on Pages

Parameter

inside the parameter list, before the parameter name

Description on edition

Return value

between As and the type: As <Description("x")> Integer (the C# [return: ...] prefix has no counterpart; <Return: ...> fails to parse)

Description on the return value of Pages

Property

before the property

DefaultValue on Year and Obsolete on Name

Field and event

before the declaration

Description on CopiesOnShelf and on the event Borrowed

Enum member

before the member

Description on Fiction

Assembly

<Assembly: Attribute(...)>, at file level before the first declaration

AssemblyMetadata

Module

<Module: Attribute(...)>, same placement

Description

The things that Visual Basic does not offer follow from that table. An attribute written before an auto-implemented property always goes on the property; the C# [field: NonSerialized] target that puts it on the compiler-generated backing field does not exist. The <Return: prefix is rejected as shown below. And attributes on lambdas (C# 10) and on local variables do not exist.

Assembly and module attributes, and the generated file

<Assembly: ...> and <Module: ...> apply to the whole assembly or module and are written outside any type, after the Imports statements and before the first declaration. Placing one after a declaration fails:

Option Strict On

Imports System.ComponentModel

Public Class Early
End Class

<Assembly: System.Reflection.AssemblyMetadata("k", "v")>

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(8,1): error BC30637: Assembly or Module attribute statements must precede any declarations in a file.

Most assembly attributes need not be written by hand. The SDK generates a file, obj/<configuration>/<framework>/<AssemblyName>.AssemblyInfo.vb, from the project’s properties. I built a project that sets Version, AssemblyVersion, Authors, Company, Product, Description, Copyright and SourceRevisionId in the project file:

<Version>2.1.0-beta.1</Version>
<AssemblyVersion>2.1.0.0</AssemblyVersion>
<Authors>Shelf team</Authors>
<Company>Shelf</Company>
<Product>Shelf catalogue</Product>
<Description>Library catalogue</Description>
<Copyright>Shelf</Copyright>
<SourceRevisionId>abc1234</SourceRevisionId>

and listed the assembly’s attributes at run time:

Option Strict On

Imports System.Reflection
Imports System.Runtime.CompilerServices

Module Program
    Sub Main()
        Dim assembly As Assembly = GetType(Program).Assembly
        Console.WriteLine("AssemblyName.Version: " & assembly.GetName().Version.ToString())
        For Each data As CustomAttributeData In assembly.CustomAttributes.OrderBy(Function(d) d.AttributeType.Name)
            Dim arguments As String = String.Join(", ", data.ConstructorArguments.Select(Function(a) a.Value.ToString()))
            Console.WriteLine($"{data.AttributeType.Name,-40}{arguments}")
        Next
    End Sub
End Module
AssemblyName.Version: 2.1.0.0
AssemblyCompanyAttribute                Shelf
AssemblyConfigurationAttribute          Debug
AssemblyCopyrightAttribute              Shelf
AssemblyDescriptionAttribute            Library catalogue
AssemblyFileVersionAttribute            2.1.0.0
AssemblyInformationalVersionAttribute   2.1.0-beta.1+abc1234
AssemblyProductAttribute                Shelf catalogue
AssemblyTitleAttribute                  App
CompilationRelaxationsAttribute         8
DebuggableAttribute                     263
RuntimeCompatibilityAttribute
TargetFrameworkAttribute                .NETCoreApp,Version=v10.0

The mapping to remember: Version becomes the informational version (the semantic version, with the source revision appended after a +) and, unless overridden, the file version (without the pre-release suffix) and the assembly version; AssemblyVersion sets the assembly’s identity; Company, Product, Description and Copyright become the attributes of those names, AssemblyTitle defaults to the assembly name, and Authors is a NuGet package property that did not produce an attribute here. TargetFramework, Debuggable and CompilationRelaxations come from the compiler.

Because the SDK already writes AssemblyTitle and AssemblyVersion, writing the same attribute yourself is a duplicate:

Option Strict On

Imports System.Reflection

<Assembly: AssemblyTitle("Shelf catalogue")>
<Assembly: AssemblyVersion("1.2.3.0")>

Module Program
    Sub Main()
        Console.WriteLine("built")
    End Sub
End Module
obj/Debug/net10.0/App.AssemblyInfo.vb(20,2): error BC30663: Attribute 'AssemblyTitleAttribute' cannot be applied multiple times.
obj/Debug/net10.0/App.AssemblyInfo.vb(21,2): error BC30663: Attribute 'AssemblyVersionAttribute' cannot be applied multiple times.

There are two fixes. Set the matching project property (the right one for almost every project), or turn the generator off with <GenerateAssemblyInfo>false</GenerateAssemblyInfo>, which makes your file the only source; the same program with that property built and printed built. Some assembly attributes have an item form: <InternalsVisibleTo Include="Shelf.Tests" /> in the project file generates <Assembly: InternalsVisibleTo("Shelf.Tests")> for you.

Custom attributes

A custom attribute is a class that derives from Attribute. Its constructor parameters become the positional arguments and its public read/write fields and properties become the named ones. AttributeUsage, itself an attribute, controls where it may be applied:

AttributeUsage setting Meaning

ValidOn (first, positional argument)

the AttributeTargets values the attribute may be applied to, combined with Or; the default is All

AllowMultiple:=True

the attribute may appear more than once on the same declaration; the default is False

Inherited:=True

derived classes, and overriding members of derived classes, see the attribute through the inheritance-aware reading APIs; the default is True

The program defines a CatalogRule attribute with a constructor argument, properties of five types (String, an enum, an Integer, a String() array and a Type), and AllowMultiple and Inherited set. It applies it to CatalogItem and its members, reads the rules back, and shows what inheritance does:

Option Strict On

Imports System.Reflection

Public Enum Audience
    Everyone
    Staff
    Admin
End Enum

<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method Or AttributeTargets.Property,
                AllowMultiple:=True, Inherited:=True)>
Public NotInheritable Class CatalogRuleAttribute
    Inherits Attribute

    Public ReadOnly Property Name As String
    Public Property Audience As Audience = Audience.Everyone
    Public Property Priority As Integer
    Public Property Tags As String()
    Public Property Handler As Type

    Public Sub New(name As String)
        Me.Name = name
    End Sub
End Class

<CatalogRule("NoDuplicates", Priority:=2, Audience:=Audience.Staff)>
<CatalogRule("MaxLoans", Tags:=New String() {"loans", "limits"}, Handler:=GetType(String))>
Public Class CatalogItem
    <CatalogRule("TitleRequired")>
    Public Overridable Property Title As String = ""

    <CatalogRule("OnlyAdmins", Audience:=Audience.Admin)>
    Public Overridable Sub Remove()
    End Sub
End Class

Public Class Magazine
    Inherits CatalogItem

    Public Overrides Sub Remove()
    End Sub

    Public Overrides Property Title As String
End Class

Module Program
    Sub Describe(label As String, rule As CatalogRuleAttribute)
        Dim tags As String = If(rule.Tags Is Nothing, "-", String.Join("/", rule.Tags))
        Dim handler As String = If(rule.Handler Is Nothing, "-", rule.Handler.Name)
        Console.WriteLine($"{label,-26}{rule.Name,-14}audience={rule.Audience,-9}priority={rule.Priority} tags={tags} handler={handler}")
    End Sub

    Sub Main()
        For Each rule As CatalogRuleAttribute In GetType(CatalogItem).GetCustomAttributes(GetType(CatalogRuleAttribute), False)
            Describe("CatalogItem", rule)
        Next
        For Each rule As CatalogRuleAttribute In GetType(Magazine).GetCustomAttributes(GetType(CatalogRuleAttribute), True)
            Describe("Magazine (inherit)", rule)
        Next
        Console.WriteLine("Magazine IsDefined (inherit False): " & GetType(Magazine).IsDefined(GetType(CatalogRuleAttribute), False))
        Console.WriteLine("Magazine IsDefined (inherit True):  " & GetType(Magazine).IsDefined(GetType(CatalogRuleAttribute), True))

        Dim removeDerived As MethodInfo = GetType(Magazine).GetMethod("Remove")
        Dim titleDerived As PropertyInfo = GetType(Magazine).GetProperty("Title")
        Console.WriteLine("Overriding method,   MemberInfo:       " & removeDerived.GetCustomAttributes(GetType(CatalogRuleAttribute), True).Length)
        Console.WriteLine("Overriding method,   Attribute class:  " & Attribute.GetCustomAttributes(removeDerived, GetType(CatalogRuleAttribute), True).Length)
        Console.WriteLine("Overriding property, MemberInfo:       " & titleDerived.GetCustomAttributes(GetType(CatalogRuleAttribute), True).Length)
        Console.WriteLine("Overriding property, Attribute class:  " & Attribute.GetCustomAttributes(titleDerived, GetType(CatalogRuleAttribute), True).Length)

        Dim titleRule As CatalogRuleAttribute = GetType(CatalogItem).GetProperty("Title").GetCustomAttribute(Of CatalogRuleAttribute)()
        Console.WriteLine("GetCustomAttribute(Of T): " & titleRule.Name)
        Try
            Dim ambiguous As CatalogRuleAttribute = GetType(CatalogItem).GetCustomAttribute(Of CatalogRuleAttribute)()
            Console.WriteLine(ambiguous.Name)
        Catch ex As AmbiguousMatchException
            Console.WriteLine("Two rules on one class: " & ex.GetType().Name)
        End Try
    End Sub
End Module
CatalogItem               NoDuplicates  audience=Staff    priority=2 tags=- handler=-
CatalogItem               MaxLoans      audience=Everyone priority=0 tags=loans/limits handler=String
Magazine (inherit)        NoDuplicates  audience=Staff    priority=2 tags=- handler=-
Magazine (inherit)        MaxLoans      audience=Everyone priority=0 tags=loans/limits handler=String
Magazine IsDefined (inherit False): False
Magazine IsDefined (inherit True):  True
Overriding method,   MemberInfo:       1
Overriding method,   Attribute class:  1
Overriding property, MemberInfo:       0
Overriding property, Attribute class:  1
GetCustomAttribute(Of T): TitleRequired
Two rules on one class: AmbiguousMatchException

What the output teaches:

  • Argument types are limited to what a constant can express: numbers, strings, Boolean, enum values, a Type (GetType(String)) and one-dimensional arrays of those (New String() {"loans", "limits"}). The program used a String, an enum, an Integer, a String() and a Type.

  • Reading a missing named argument gives the property’s default: Audience is Everyone and Priority is 0 for the second rule, which set neither.

  • GetCustomAttribute(Of T) is for a single attribute. With AllowMultiple:=True and two attributes present it throws AmbiguousMatchException; use GetCustomAttributes for lists.

  • The inherit flag is honoured for classes and methods but not for properties and events when you read through MemberInfo. The overriding Magazine.Title reported 0 rules through MemberInfo.GetCustomAttributes(type, True) and 1 through Attribute.GetCustomAttributes(member, type, True). If you must support inherited property attributes, use the Attribute class methods (or the CustomAttributeExtensions ones, which call them).

  • IsDefined does not create an attribute instance. It is the cheap way to test for presence, because reading an attribute runs its constructor.

What arguments are allowed

An attribute argument must be a constant the compiler can evaluate: a literal, a Const, a concatenation of constants, NameOf(...), an enum value, GetType(...), or an array of those. A value only known at run time, including a Shared ReadOnly field and a method call, is rejected. The program below uses one example of each:

Option Strict On

Imports System.ComponentModel

Public Class Computed
    Private Const Fixed As String = "fixed"
    Private Shared ReadOnly Dynamic As String = "dynamic"

    <Description(Fixed)>
    Public Property A As String

    <Description(Dynamic)>
    Public Property B As String

    <Description(String.Concat("a", "b"))>
    Public Property C As String

    <Description("a" & "b")>
    Public Property D As String

    <Description(NameOf(A))>
    Public Property E As String
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(12,18): error BC30059: Constant expression is required.
Program.vb(15,18): error BC30059: Constant expression is required.

Fixed (a Const), the concatenation "a" & "b" and NameOf(A) compile; the Shared ReadOnly field and String.Concat do not. The shared helpers for the following failing programs live in one file, Rules.vb. It declares RuleAttribute (valid on classes only, one required constructor argument, a read-only Name and a writable Priority), AnyRuleAttribute and a plain class Plain that is not an attribute:

Option Strict On

<AttributeUsage(AttributeTargets.Class)>
Public NotInheritable Class RuleAttribute
    Inherits Attribute

    Public ReadOnly Property Name As String
    Public Property Priority As Integer

    Public Sub New(name As String)
        Me.Name = name
    End Sub
End Class

<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method)>
Public NotInheritable Class AnyRuleAttribute
    Inherits Attribute
End Class

Public Class Plain
End Class

Attribute errors

Each failing program uses Rules.vb and was built on its own; the compiler messages are those the SDK printed. Every program also has Imports System.ComponentModel for Description, and Rules.vb is not repeated in the listings.

Applying a single-use attribute twice:

Option Strict On

Imports System.ComponentModel

<Rule("a"), Rule("b")>
Public Class Twice
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(5,13): error BC30663: Attribute 'RuleAttribute' cannot be applied multiple times.

Applying an attribute to a declaration its AttributeUsage does not allow (RuleAttribute is valid on classes only):

Option Strict On

Imports System.ComponentModel

Public Class WrongTarget
    <Rule("a")>
    Public Sub Run()
    End Sub

    <Rule("b")>
    Public Property Title As String
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(6,6): error BC30662: Attribute 'RuleAttribute' cannot be applied to 'Run' because the attribute is not valid on this declaration type.
Program.vb(10,6): error BC30662: Attribute 'RuleAttribute' cannot be applied to 'Title' because the attribute is not valid on this declaration type.

Wrong constructor arguments, unknown or read-only named arguments, wrong types:

Option Strict On

Imports System.ComponentModel

<Rule>
Public Class NoArgument
End Class

<Rule("a", "b")>
Public Class TooMany
End Class

<Rule("a", Missing:=1)>
Public Class UnknownNamed
End Class

<Rule("a", Name:="again")>
Public Class ReadOnlyNamed
End Class

<Rule(42)>
Public Class WrongType
End Class

<Rule("a", Priority:="high")>
Public Class WrongNamedType
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(5,2): error BC30455: Argument not specified for parameter 'name' of 'Public Sub New(name As String)'.
Program.vb(9,12): error BC30057: Too many arguments to 'Public Sub New(name As String)'.
Program.vb(13,12): error BC30661: Field or property 'Missing' is not found.
Program.vb(17,12): error BC31501: 'ReadOnly' attribute property 'Name' cannot be the target of an assignment.
Program.vb(21,7): error BC30512: Option Strict On disallows implicit conversions from 'Integer' to 'String'.
Program.vb(25,22): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Integer'.
Program.vb(25,22): error BC30934: Conversion from 'String' to 'Integer' cannot occur in a constant expression used as an argument to an attribute.

A class that is not an attribute:

Option Strict On

<Plain>
Public Class AlsoNot
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(3,2): error BC31504: 'Plain' cannot be used as an attribute because it does not inherit from 'System.Attribute'.

A generic attribute:

Option Strict On

Imports System.ComponentModel

Public NotInheritable Class KindAttribute(Of T)
    Inherits Attribute
End Class

<Kind(Of Integer)>
Public Class UsesGeneric
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(9,2): error BC32066: Type arguments are not valid because attributes cannot be generic.

An attribute on a local variable:

Option Strict On

Imports System.ComponentModel

Public Class Misplaced
    <Description("local")>
    Public Sub Run()
        <Description("x")> Dim local As Integer = 1
        Console.WriteLine(local)
    End Sub
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(8,9): error BC30660: Attributes cannot be applied to local variables.

The last two programs show Visual Basic-specific limits: attributes cannot be generic, even though C# 11 allows them, and there are no attributes on locals. The two target forms that the other languages offer also fail, each with a syntax error rather than a helpful message:

Option Strict On

Imports System.ComponentModel

Public Class FieldTarget
    <field: NonSerialized>
    Public Property A As Integer
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(6,11): error BC30636: '>' expected.
Program.vb(6,13): error BC30188: Declaration expected.
Option Strict On

Imports System.ComponentModel

Public Class ReturnTarget
    <Return: Description("r")>
    Public Function F() As Integer
        Return 1
    End Function
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(6,12): error BC30636: '>' expected.
Program.vb(6,14): error BC30188: Declaration expected.
Program.vb(6,6): error BC30183: Keyword is not valid as an identifier.

The messages mislead: they do not say that the field: target does not exist or that Return is a keyword. The correct spelling of the return-value attribute is shown in the first program of this page.

Writing attribute lists

All four of these forms (stacked lists, one list over two lines, a trailing underscore, and the list on the same line as the declaration) compile and produce the same four properties:

Option Strict On

Imports System.ComponentModel

Public Class Two
    <Description("a")> <Category("b")>
    Public Property A As Integer

    <Description("a"),
     Category("b")>
    Public Property B As Integer

    <Description("c"), Category("d")> Public Property C As Integer

    <Description("e") _
    > Public Property D As Integer
End Class

Module Program
    Sub Main()
        Console.WriteLine(GetType(Two).GetProperties().Length)
    End Sub
End Module
4

The common attributes

Attribute Namespace What it does

Obsolete

System

marks a member as deprecated; <Obsolete("message")> gives warning BC40000, <Obsolete("message", True)> makes use an error, BC30668 (below)

Conditional

System.Diagnostics

removes calls to the method unless a constant is defined at the call site; see Conditional Compilation and Directives

Flags

System

lets an enumeration be used as a bit set; see Enumerations

DebuggerDisplay, DebuggerStepThrough, DebuggerHidden

System.Diagnostics

control how the debugger shows a type and whether it steps into code; DebuggerDisplay("{Title,nq}") is in the first program

MethodImpl

System.Runtime.CompilerServices

JIT hints such as NoInlining (used in the plugin example to make unloading observable)

Extension

System.Runtime.CompilerServices

marks an extension method; in Visual Basic it is written on a Sub or Function of a module; see Extension Methods and Partial Methods

InternalsVisibleTo

System.Runtime.CompilerServices

lets a named assembly (typically the tests) see Friend members

ThreadStatic

System

gives each thread its own copy of a Shared field; see Threads, Parallelism and Synchronization

DllImport, MarshalAs

System.Runtime.InteropServices

native calls and marshalling; see Native Interop: Declare and P/Invoke

Description, Category, DefaultValue, Browsable

System.ComponentModel

design-time and serializer metadata; no run-time effect unless something reads them

SupportedOSPlatform, RequiresUnreferencedCode, DynamicallyAccessedMembers, RequiresDynamicCode

System.Runtime.Versioning, System.Diagnostics.CodeAnalysis

platform and trimming annotations, below

StandardModule, ParamArray, DefaultMember

Microsoft.VisualBasic.CompilerServices, System, System.Reflection

written by the compiler for a Module, a ParamArray parameter and a default property (DefaultMemberAttribute("Item")); all three are visible in the reflection listing below

HideModuleName

Microsoft.VisualBasic

you write it yourself on a module whose name should not appear in IntelliSense, as the My machinery does

Obsolete is the one every project meets. With the message only, use is a warning; with True as the second argument it is an error:

Option Strict On

Imports System.ComponentModel

Module Program
    <Obsolete("Use NewName", True)>
    Sub OldName()
    End Sub

    <Obsolete("Use NewName")>
    Sub OlderName()
    End Sub

    Sub Main()
        OldName()
        OlderName()
    End Sub
End Module
Program.vb(15,9): error BC30668: 'Public Sub OldName()' is obsolete: 'Use NewName'.
Program.vb(16,9): warning BC40000: 'Public Sub OlderName()' is obsolete: 'Use NewName'.

The program contains the error and the warning, so with TreatWarningsAsErrors on, BC40000 is an error too; the build stopped on BC30668 either way.

Caller information

Caller information lets a method learn where it was called from without the caller passing anything. A method declares optional parameters with default values and decorates them with attributes from System.Runtime.CompilerServices. When a caller omits the argument, the compiler writes the value into the call at compile time. Visual Basic 11 (Visual Studio 2012) introduced the first three; CallerArgumentExpression arrived with Visual Basic 17.0 (Visual Studio 2022 17.0, .NET 6).

Attribute Parameter type Value supplied

CallerMemberName

String

the name of the calling member (see the table of special names below)

CallerFilePath

String

the full path of the caller’s source file at compile time

CallerLineNumber

Integer

the line number of the call in that file

CallerArgumentExpression("parameterName")

String

the source text of the argument passed to the other parameter named in the attribute

The first program defines a logging helper that takes all three of the older attributes and calls it from every kind of member, so you can see exactly what the compiler writes:

Option Strict On

Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.CompilerServices

Module Log
    Sub Write(message As String,
              <CallerMemberName> Optional member As String = "",
              <CallerFilePath> Optional file As String = "",
              <CallerLineNumber> Optional line As Integer = 0)
        Console.WriteLine($"{message,-22} member={member} file={Path.GetFileName(file)} line={line}")
    End Sub
End Module

Public Class Book
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Private _title As String = ""

    Shared Sub New()
        Log.Write("shared constructor")
    End Sub

    Public Sub New()
        Log.Write("constructor")
    End Sub

    Public Property Title As String
        Get
            Log.Write("property getter")
            Return _title
        End Get
        Set(value As String)
            _title = value
            Log.Write("property setter")
            OnChanged()
        End Set
    End Property

    Private Sub OnChanged(<CallerMemberName> Optional name As String = "")
        Console.WriteLine("PropertyChanged for " & name)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
    End Sub

    Public Shared Operator +(left As Book, right As Book) As Book
        Log.Write("operator")
        Return left
    End Operator

    Public Shared Widening Operator CType(value As Book) As String
        Log.Write("conversion operator")
        Return value._title
    End Operator

    Default Public ReadOnly Property Item(index As Integer) As Char
        Get
            Log.Write("default property")
            Return _title(index)
        End Get
    End Property

End Class

Module Program
    Sub Plain()
        Log.Write("sub")
    End Sub

    Async Function LoadAsync() As Task
        Log.Write("before the await")
        Await Task.Delay(1)
        Log.Write("after the await")
    End Function

    Iterator Function Numbers() As IEnumerable(Of Integer)
        Log.Write("inside an iterator")
        Yield 1
    End Function

    Sub Main()
        Log.Write("Main")
        Plain()
        Dim lambda As Action = Sub() Log.Write("lambda")
        lambda()
        Dim book As New Book()
        book.Title = "Dune"
        Dim ignored As String = book.Title
        Dim sum As Book = book + book
        Dim text As String = book
        Dim first As Char = book(0)
        LoadAsync().GetAwaiter().GetResult()
        For Each number As Integer In Numbers()
        Next
        Log.Write("explicit values", "custom member", "custom.vb", 42)
        Log.Write("named argument", line:=7)
    End Sub
End Module
Main                   member=Main file=Program.vb line=84
sub                    member=Plain file=Program.vb line=69
lambda                 member=Main file=Program.vb line=86
shared constructor     member=.cctor file=Program.vb line=24
constructor            member=.ctor file=Program.vb line=28
property setter        member=Title file=Program.vb line=38
PropertyChanged for Title
property getter        member=Title file=Program.vb line=33
operator               member=op_Addition file=Program.vb line=49
conversion operator    member=op_Implicit file=Program.vb line=54
default property       member=Item file=Program.vb line=60
before the await       member=LoadAsync file=Program.vb line=73
after the await        member=LoadAsync file=Program.vb line=75
inside an iterator     member=Numbers file=Program.vb line=79
explicit values        member=custom member file=custom.vb line=42
named argument         member=Main file=Program.vb line=7

Read the output for the special cases:

Caller CallerMemberName

A Sub, Function, property accessor

the name; a getter and a setter both give the property name (Title)

A constructor and a shared constructor

.ctor and .cctor

An operator

the metadata name: op_Addition; a Widening conversion operator is op_Implicit

A default property

Item

A lambda

the enclosing member (Main), not a compiler-generated name

An Async or Iterator method

the method’s own name, before and after an Await

An explicit value, positional or named

the value you wrote; line:=7 supplies the line and leaves the member to be filled in

The file value is the full path on the machine that compiled the code. That is useful in logs on a developer machine and a leak of build-machine layout in a shipped binary. The program in the table printed only Path.GetFileName(file); here is the raw value, and the same program built with <PathMap>$(MSBuildProjectDirectory)=/src</PathMap> in the project file:

Option Strict On

Imports System.Runtime.CompilerServices

Module Program
    Function Locate(<CallerFilePath> Optional file As String = "") As String
        Return file
    End Function

    Sub Main()
        Console.WriteLine(Locate())
    End Sub
End Module

Default build:

/w/a12/Program.vb

With the PathMap property:

/src/Program.vb

PathMap rewrites the path prefix for every path the compiler embeds, including the CallerFilePath text and the paths in debug symbols; the exact value is whatever you choose, so builds on different machines can produce the same binary.

The attribute is advisory

Visual Basic does not check that a caller-information attribute is on a suitable parameter, and when it is not, the attribute is silently ignored:

Option Strict On

Imports System.Runtime.CompilerServices

Module Program
    Sub WrongType(<CallerLineNumber> Optional line As String = "unset")
        Console.WriteLine("String parameter received: " & line)
    End Sub

    Sub WrongName(<CallerMemberName> Optional name As Integer = -1)
        Console.WriteLine("Integer parameter received: " & name)
    End Sub

    Sub Mandatory(<CallerMemberName> name As String)
        Console.WriteLine("Mandatory parameter received: " & name)
    End Sub

    Sub Main()
        WrongType()
        WrongName()
        Mandatory("typed")
    End Sub
End Module
String parameter received: unset
Integer parameter received: -1
Mandatory parameter received: typed

CallerLineNumber on a String parameter and CallerMemberName on an Integer parameter were ignored (the defaults arrived), and Mandatory, whose parameter has no Optional, behaved like an ordinary parameter. The attribute only works on an optional parameter of the right type. If you leave out the optional argument, a later positional argument cannot be skipped: write Trace(, 7) (an empty position) or use a named argument, as in the next listing.

Trace()            ' member filled in, extra = 0
Trace(extra:=5)    ' member filled in, extra = 5
Trace("by hand")   ' the member name is the text you passed
Trace(, 7)         ' empty position: member filled in, extra = 7

These four calls were compiled and run against Sub Trace(<CallerMemberName> Optional member As String = "", Optional extra As Integer = 0):

member=Main extra=0
member=Main extra=5
member=typed by hand extra=0
member=Main extra=7

CallerArgumentExpression

CallerArgumentExpression("name") gives a helper the source text of the argument that the caller wrote for another parameter. It exists for guard clauses and test assertions that want to say "this condition failed" with the condition itself. The program defines Require and Describe, and also uses the framework’s own users of the attribute, ArgumentNullException.ThrowIfNull and ArgumentException.ThrowIfNullOrEmpty:

Option Strict On

Imports System.Runtime.CompilerServices

Public Class RuleViolationException
    Inherits Exception

    Public ReadOnly Property Expression As String

    Public Sub New(message As String, expression As String)
        MyBase.New(message)
        Me.Expression = expression
    End Sub
End Class

Module Guard
    Sub Require(condition As Boolean,
                <CallerArgumentExpression("condition")> Optional expression As String = Nothing)
        If Not condition Then
            Throw New RuleViolationException("Rule failed: " & expression, expression)
        End If
    End Sub

    Function Describe(value As Object,
                      <CallerArgumentExpression("value")> Optional expression As String = Nothing) As String
        Return $"{expression} = {value}"
    End Function
End Module

Module Program
    Sub Main()
        Dim copies As Integer = 3
        Dim title As String = "Dune"
        Console.WriteLine(Describe(copies))
        Console.WriteLine(Describe(copies * 2 + 1))
        Console.WriteLine(Describe(title.ToUpperInvariant()))
        Console.WriteLine(Describe(  copies   +   1  ))
        Console.WriteLine(Describe(
            copies))

        Try
            Require(copies > 5)
        Catch ex As RuleViolationException
            Console.WriteLine(ex.Message)
        End Try

        Try
            Require(title.Length = 0 OrElse copies < 0)
        Catch ex As RuleViolationException
            Console.WriteLine(ex.Message)
        End Try

        Try
            Dim missing As String = Nothing
            ArgumentNullException.ThrowIfNull(missing)
        Catch ex As ArgumentNullException
            Console.WriteLine(ex.Message.Replace(Environment.NewLine, " "))
        End Try

        Try
            ArgumentNullException.ThrowIfNull(Nothing, "explicit name")
        Catch ex As ArgumentNullException
            Console.WriteLine(ex.Message.Replace(Environment.NewLine, " "))
        End Try

        Try
            ArgumentException.ThrowIfNullOrEmpty(Nothing)
        Catch ex As ArgumentException
            Console.WriteLine(ex.GetType().Name & ": " & ex.Message.Replace(Environment.NewLine, " "))
        End Try
    End Sub
End Module
copies = 3
copies * 2 + 1 = 7
title.ToUpperInvariant() = DUNE
copies   +   1 = 4
copies = 3
Rule failed: copies > 5
Rule failed: title.Length = 0 OrElse copies < 0
Value cannot be null. (Parameter 'missing')
Value cannot be null. (Parameter 'explicit name')
ArgumentNullException: Value cannot be null. (Parameter 'Nothing')

Notes on the output:

  • The text is the argument as written, trimmed at both ends but not normalised inside: copies + 1 kept its spaces. An argument spread over two lines gave just copies, the text between the parentheses with the leading newline removed.

  • The BCL guard helpers work from Visual Basic, because the attribute is honoured at the call site: ThrowIfNull(missing) said Parameter 'missing', and an argument that is the literal Nothing produced the parameter name Nothing.

  • A name that is not a parameter is a warning, not an error, and the attribute then has no effect:

Program.vb(12,45): warning BC42505: The CallerArgumentExpressionAttribute applied to parameter 'text' will have no effect. It is applied with an invalid parameter name.
  • The expression is source text copied at compile time; it is not evaluated again. Your identifiers end up in the compiled binary, so do not use it for messages meant for users.

NameOf

NameOf (Visual Basic 14, Visual Studio 2015) turns a name in code into a string at compile time. The string is a constant; it is what you use for parameter names in exceptions, for PropertyChanged notifications, for log messages, and for attribute arguments. When the member is renamed, the string follows it.

Option Strict On

Imports System.Collections.Generic
Imports System.IO

Namespace Shelf
    Public Class Book
        Public Property Title As String = ""
        Public Shared Property Count As Integer
        Public Sub Rename(newTitle As String)
            Title = newTitle
        End Sub
        Public Sub Rename(newTitle As String, force As Boolean)
            Title = newTitle
        End Sub
    End Class
End Namespace

Public Class Holder
    Public Property Title As String = ""
    Public Property Name As String = ""

    Public Function Names() As String()
        Return {NameOf(Title), NameOf(Me.Name), NameOf(Holder.Title), NameOf(Holder)}
    End Function
End Class

Module Program
    Private Const TitleName As String = NameOf(Shelf.Book.Title)

    Sub Show(code As String, value As String)
        Console.WriteLine(code.PadRight(36) & value)
    End Sub

    Sub Main()
        Dim copies As Integer = 3
        Dim parts As New List(Of String)()

        Show("NameOf(copies)", NameOf(copies))
        Show("NameOf(parts)", NameOf(parts))
        Show("NameOf(Shelf.Book)", NameOf(Shelf.Book))
        Show("NameOf(Shelf.Book.Title)", NameOf(Shelf.Book.Title))
        Show("NameOf(Shelf.Book.Count)", NameOf(Shelf.Book.Count))
        Show("NameOf(Shelf.Book.Rename)", NameOf(Shelf.Book.Rename))
        Show("NameOf(Shelf)", NameOf(Shelf))
        Show("NameOf(System.IO)", NameOf(System.IO))
        Show("NameOf(System.Console.WriteLine)", NameOf(System.Console.WriteLine))
        Show("NameOf(List(Of String))", NameOf(List(Of String)))
        Show("NameOf(List(Of String).Count)", NameOf(List(Of String).Count))
        Show("NameOf(Program)", NameOf(Program))
        Show("NameOf(Main)", NameOf(Main))
        Show("Const TitleName", TitleName)
        Show("NameOf(copies) in a Select Case", Describe(NameOf(copies)))
        Show("Holder.Names()", String.Join(",", New Holder().Names()))
        Show("NameOf(parts).Length", NameOf(parts).Length.ToString())
    End Sub

    Function Describe(name As String) As String
        Select Case name
            Case NameOf(Describe)
                Return "the function itself"
            Case "copies"
                Return "copies matched as a constant"
            Case Else
                Return "other"
        End Select
    End Function
End Module
NameOf(copies)                      copies
NameOf(parts)                       parts
NameOf(Shelf.Book)                  Book
NameOf(Shelf.Book.Title)            Title
NameOf(Shelf.Book.Count)            Count
NameOf(Shelf.Book.Rename)           Rename
NameOf(Shelf)                       Shelf
NameOf(System.IO)                   IO
NameOf(System.Console.WriteLine)    WriteLine
NameOf(List(Of String))             List
NameOf(List(Of String).Count)       Count
NameOf(Program)                     Program
NameOf(Main)                        Main
Const TitleName                     Title
NameOf(copies) in a Select Case     copies matched as a constant
Holder.Names()                      Title,Name,Title,Holder
NameOf(parts).Length                5

The rule: NameOf returns the last identifier of the expression, with no qualification, no generic arguments and no parentheses. NameOf(Shelf.Book.Title) is Title; NameOf(List(Of String)) is List; NameOf(Shelf) for a namespace is Shelf. It works on locals, parameters, members (including Me.Name), types, namespaces and methods (all overloads of Rename map to Rename), and the result is a constant: it can initialise a Const (TitleName above), appear in a Case label or in an attribute argument. The things it rejects:

Option Strict On

Module Program
    Sub Main()
        Dim open As String = NameOf(Dictionary(Of ,))
        Dim keyword As String = NameOf(Integer)
        Dim literal As String = NameOf(1 + 1)
        Dim missing As String = NameOf(DoesNotExist)
        Dim call1 As String = NameOf(Console.WriteLine("x"))
        Console.WriteLine(open & keyword & literal & missing & call1)
    End Sub
End Module
Program.vb(5,51): error BC30182: Type expected.
Program.vb(5,52): error BC30182: Type expected.
Program.vb(6,40): error BC37244: This expression does not have a name.
Program.vb(7,40): error BC37244: This expression does not have a name.
Program.vb(9,38): error BC37244: This expression does not have a name.
Option Strict On

Module Program
    Sub Main()
        Dim missing As String = NameOf(DoesNotExist)
        Console.WriteLine(missing)
    End Sub
End Module
Program.vb(5,40): error BC30451: 'DoesNotExist' is not declared. It may be inaccessible due to its protection level.

An open generic type (Dictionary(Of ,)), a keyword type (Integer), a general expression (1 + 1, a call) are all errors, and so is a name that does not exist: NameOf is checked by the compiler, which is the reason to prefer it to a string literal. The ArgumentException in the reflection program below uses it for the parameter name in exactly this way.

Reflection

Reflection is the API of System.Reflection and System.Type that describes types, members and assemblies and lets you act on that description. The entry point is the Type object.

Getting a Type

The program below gets a Type three ways (the GetType operator, the GetType() method, and TypeOf ... Is as the type test) and lets the Type answer questions about itself. In Visual Basic GetType(X) is the operator (C# typeof) and obj.GetType() is the method (the same name, different things; the operator needs a type name, the method needs an object):

Option Strict On

Imports System.Reflection

Public Enum LoanState
    Active = 1
    Returned = 2
End Enum

Public Interface IRenewable
    Sub Renew()
End Interface

Public MustInherit Class CatalogItem
    Public MustOverride Function Describe() As String
End Class

Public NotInheritable Class Magazine
    Inherits CatalogItem
    Implements IRenewable

    Public Overrides Function Describe() As String
        Return "magazine"
    End Function

    Public Sub Renew() Implements IRenewable.Renew
    End Sub
End Class

Public Structure Isbn
    Public Value As String
End Structure

Module Program
    Sub Row(label As String, type As Type)
        Console.WriteLine($"{label,-26}{type.Name,-14}class={type.IsClass,-6} value={type.IsValueType,-6} iface={type.IsInterface,-6} enum={type.IsEnum,-6} abstract={type.IsAbstract,-6} sealed={type.IsSealed,-6} generic={type.IsGenericType}")
    End Sub

    Sub Main()
        Dim item As CatalogItem = New Magazine()
        Dim ids As Object = 42

        Console.WriteLine("-- three ways to get a Type")
        Console.WriteLine("GetType(Magazine):          " & GetType(Magazine).Name)
        Console.WriteLine("item.GetType():             " & item.GetType().Name & "   (the run-time type)")
        Console.WriteLine("GetType(CatalogItem):       " & GetType(CatalogItem).Name & "   (the declared type)")
        Console.WriteLine("ids.GetType():              " & ids.GetType().FullName)
        Console.WriteLine("TypeOf item Is Magazine:    " & (TypeOf item Is Magazine))
        Console.WriteLine("TypeOf item Is IRenewable:  " & (TypeOf item Is IRenewable))
        Console.WriteLine("GetType(List(Of String)):   " & GetType(List(Of String)).ToString())
        Console.WriteLine("GetType(List(Of )):         " & GetType(List(Of )).ToString())
        Console.WriteLine("GetType(Dictionary(Of ,)):  " & GetType(Dictionary(Of ,)).ToString())
        Console.WriteLine("GetType(Integer()):         " & GetType(Integer()).ToString())
        Console.WriteLine("GetType(Integer):           " & GetType(Integer).FullName)

        Console.WriteLine("-- what a Type can tell you")
        Row("Magazine", GetType(Magazine))
        Row("CatalogItem", GetType(CatalogItem))
        Row("IRenewable", GetType(IRenewable))
        Row("LoanState", GetType(LoanState))
        Row("Isbn", GetType(Isbn))
        Row("Integer", GetType(Integer))
        Row("List(Of String)", GetType(List(Of String)))
        Row("String", GetType(String))

        Console.WriteLine("-- relations")
        Console.WriteLine("Magazine.BaseType:          " & GetType(Magazine).BaseType.Name)
        Console.WriteLine("Isbn.BaseType:              " & GetType(Isbn).BaseType.Name)
        Console.WriteLine("LoanState.BaseType:         " & GetType(LoanState).BaseType.Name)
        Console.WriteLine("Magazine interfaces:        " & String.Join(", ", GetType(Magazine).GetInterfaces().Select(Function(i) i.Name)))
        Console.WriteLine("IRenewable.IsAssignableFrom(Magazine): " & GetType(IRenewable).IsAssignableFrom(GetType(Magazine)))
        Console.WriteLine("CatalogItem.IsAssignableFrom(Magazine): " & GetType(CatalogItem).IsAssignableFrom(GetType(Magazine)))
        Console.WriteLine("Magazine.IsSubclassOf(CatalogItem):    " & GetType(Magazine).IsSubclassOf(GetType(CatalogItem)))
        Console.WriteLine("LoanState names: " & String.Join(", ", [Enum].GetNames(GetType(LoanState))))
        Console.WriteLine("List(Of String) type arguments: " & GetType(List(Of String)).GetGenericArguments()(0).Name)

        Console.WriteLine("-- the assembly")
        Dim assembly As Assembly = GetType(Program).Assembly
        Console.WriteLine("Name:         " & assembly.GetName().Name)
        Console.WriteLine("Entry point:  " & assembly.EntryPoint.DeclaringType.Name & "." & assembly.EntryPoint.Name)
        Console.WriteLine("Same as GetExecutingAssembly: " & (assembly Is Assembly.GetExecutingAssembly()))
        Console.WriteLine("Same as GetEntryAssembly:     " & (assembly Is Assembly.GetEntryAssembly()))
        Console.WriteLine("Exported types: " & String.Join(", ", assembly.GetExportedTypes().Select(Function(t) t.Name).OrderBy(Function(n) n)))
        Console.WriteLine("All types:      " & String.Join(", ", assembly.GetTypes().Select(Function(t) t.Name).OrderBy(Function(n) n)))
        Console.WriteLine("References:     " & String.Join(", ", assembly.GetReferencedAssemblies().Select(Function(r) r.Name).OrderBy(Function(n) n)))
    End Sub
End Module
-- three ways to get a Type
GetType(Magazine):          Magazine
item.GetType():             Magazine   (the run-time type)
GetType(CatalogItem):       CatalogItem   (the declared type)
ids.GetType():              System.Int32
TypeOf item Is Magazine:    True
TypeOf item Is IRenewable:  True
GetType(List(Of String)):   System.Collections.Generic.List`1[System.String]
GetType(List(Of )):         System.Collections.Generic.List`1[T]
GetType(Dictionary(Of ,)):  System.Collections.Generic.Dictionary`2[TKey,TValue]
GetType(Integer()):         System.Int32[]
GetType(Integer):           System.Int32
-- what a Type can tell you
Magazine                  Magazine      class=True   value=False  iface=False  enum=False  abstract=False  sealed=True   generic=False
CatalogItem               CatalogItem   class=True   value=False  iface=False  enum=False  abstract=True   sealed=False  generic=False
IRenewable                IRenewable    class=False  value=False  iface=True   enum=False  abstract=True   sealed=False  generic=False
LoanState                 LoanState     class=False  value=True   iface=False  enum=True   abstract=False  sealed=True   generic=False
Isbn                      Isbn          class=False  value=True   iface=False  enum=False  abstract=False  sealed=True   generic=False
Integer                   Int32         class=False  value=True   iface=False  enum=False  abstract=False  sealed=True   generic=False
List(Of String)           List`1        class=True   value=False  iface=False  enum=False  abstract=False  sealed=False  generic=True
String                    String        class=True   value=False  iface=False  enum=False  abstract=False  sealed=True   generic=False
-- relations
Magazine.BaseType:          CatalogItem
Isbn.BaseType:              ValueType
LoanState.BaseType:         Enum
Magazine interfaces:        IRenewable
IRenewable.IsAssignableFrom(Magazine): True
CatalogItem.IsAssignableFrom(Magazine): True
Magazine.IsSubclassOf(CatalogItem):    True
LoanState names: Active, Returned
List(Of String) type arguments: String
-- the assembly
Name:         App
Entry point:  Program.Main
Same as GetExecutingAssembly: True
Same as GetEntryAssembly:     True
Exported types: CatalogItem, IRenewable, Isbn, LoanState, Magazine
All types:      _Closure$__, CatalogItem, IRenewable, Isbn, LoanState, Magazine, Program
References:     Microsoft.VisualBasic.Core, System.Collections, System.Console, System.Linq, System.Runtime

Source: Type class

Points to take from it: GetType(Magazine) and GetType(CatalogItem) are declared types that cost nothing at run time, while item.GetType() is the run-time type and depends on the object. A Module is compiled to a sealed class (see the next listing). An interface is abstract and not a class. An open generic type is written with empty type-argument positions, GetType(Dictionary(Of ,)). And Program (a Friend module by default) is not in GetExportedTypes, but the compiler’s hidden closure class _Closure$__ is in GetTypes — a reminder that compiler-generated types are real types.

What the compiler generates

Visual Basic syntax produces more members than you wrote. This is the full member list of a class with an auto-implemented property, a WithEvents field, two kinds of event, a default property, a static local, a ParamArray and a lambda that captures a local. The first column is the kind of member:

Option Strict On

Imports System.Reflection

Public Class Cover
    Public Event Opened As EventHandler
End Class

Public Class Book
    Private WithEvents Front As New Cover()

    Public Property Title As String = ""
    Public ReadOnly Property Pages As Integer

    Public Event Borrowed As EventHandler
    Public Event Returned(sender As Object, e As EventArgs)

    Default Public ReadOnly Property Item(index As Integer) As Char
        Get
            Return Title(index)
        End Get
    End Property

    Public Function Count(Optional step1 As Integer = 1) As Integer
        Static calls As Integer
        calls += step1
        Return calls
    End Function

    Public Function Match(ParamArray tags As String()) As Integer
        Dim wanted As Integer = tags.Length
        Dim filter As Func(Of String, Boolean) = Function(tag) tag.Length = wanted
        Return tags.Count(filter)
    End Function

    Private Sub Front_Opened(sender As Object, e As EventArgs) Handles Front.Opened
    End Sub
End Class

Module Helpers
    Public Sub Hello()
    End Sub
End Module

Module Program
    Sub List(type As Type)
        Console.WriteLine("== " & type.Name)
        Dim flags As BindingFlags = BindingFlags.Instance Or BindingFlags.Static Or BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.DeclaredOnly
        For Each member As MemberInfo In type.GetMembers(flags).OrderBy(Function(m) m.MemberType.ToString()).ThenBy(Function(m) m.Name)
            Console.WriteLine($"  {member.MemberType,-12}{member.Name}")
        Next
    End Sub

    Sub Main()
        List(GetType(Book))
        Console.WriteLine("== nested types of Book")
        For Each nested As Type In GetType(Book).GetNestedTypes(BindingFlags.NonPublic Or BindingFlags.Public)
            Console.WriteLine("  " & nested.Name)
        Next
        Console.WriteLine("== Helpers (a Module)")
        Dim helpers As Type = GetType(Helpers)
        Console.WriteLine($"  IsAbstract={helpers.IsAbstract} IsSealed={helpers.IsSealed}")
        For Each data As CustomAttributeData In helpers.CustomAttributes
            Console.WriteLine("  attribute: " & data.AttributeType.Name)
        Next
        Console.WriteLine("== attributes behind VB syntax")
        For Each data As CustomAttributeData In GetType(Book).GetMethod("Match").GetParameters()(0).CustomAttributes
            Console.WriteLine("  ParamArray parameter: " & data.AttributeType.Name)
        Next
        For Each data As CustomAttributeData In GetType(Book).CustomAttributes
            Console.WriteLine("  Book: " & data.AttributeType.Name & " " & String.Join(",", data.ConstructorArguments.Select(Function(a) a.Value)))
        Next
    End Sub
End Module
== Book
  Constructor .ctor
  Event       Borrowed
  Event       Returned
  Field       _Front
  Field       _Pages
  Field       _Title
  Field       $STATIC$Count$20188$calls
  Field       BorrowedEvent
  Field       ReturnedEvent
  Method      add_Borrowed
  Method      add_Returned
  Method      Count
  Method      Front_Opened
  Method      get_Front
  Method      get_Item
  Method      get_Pages
  Method      get_Title
  Method      Match
  Method      remove_Borrowed
  Method      remove_Returned
  Method      set_Front
  Method      set_Title
  NestedType  _Closure$__24-0
  NestedType  ReturnedEventHandler
  Property    Front
  Property    Item
  Property    Pages
  Property    Title
== nested types of Book
  ReturnedEventHandler
  _Closure$__24-0
== Helpers (a Module)
  IsAbstract=False IsSealed=True
  attribute: StandardModuleAttribute
== attributes behind VB syntax
  ParamArray parameter: ParamArrayAttribute
  Book: DefaultMemberAttribute Item

Reading the listing as a table of Visual Basic features:

You wrote The compiler generated

Public Property Title As String = ""

a private field _Title and get_Title/set_Title; an auto-implemented property’s field is named with an underscore plus the property name

Private WithEvents Front As New Cover()

a field _Front, a property Front whose setter wires and unwires the Handles clause, and the handler Front_Opened

Public Event Borrowed As EventHandler

a field BorrowedEvent, add_Borrowed, remove_Borrowed

Public Event Returned(sender As Object, e As EventArgs)

all of the above plus a new nested delegate type ReturnedEventHandler

Default Public ReadOnly Property Item(...)

a property Item plus DefaultMemberAttribute("Item") on the class

Static calls As Integer inside Count

a field named $STATIC$Count$20188$calls (the numeric part is generated; do not rely on it) that holds the value between calls

a lambda capturing a local

a nested class _Closure$__24-0 with the captured variable and the lambda body

ParamArray tags As String()

ParamArrayAttribute on the parameter

Module Helpers

a class that is Sealed, not Abstract, and carries StandardModuleAttribute

The practical lessons: a reflection loop over GetFields(BindingFlags.NonPublic Or BindingFlags.Instance) sees _Front, _Pages and _Title, so serializers and mappers that use fields instead of properties see the Visual Basic names; and GetMembers() includes the special-name accessor methods unless you filter with IsSpecialName.

Creating objects and calling members

Activator.CreateInstance and ConstructorInfo.Invoke create objects; MethodInfo.Invoke, PropertyInfo.GetValue/SetValue and FieldInfo.GetValue/SetValue use their members. The program creates a Book and drives it entirely by reflection, and triggers each of the failures a reflection caller must expect:

Option Strict On

Imports System.Reflection

Public Class Book
    Private _isbn As String = "978-0-00-000000-2"
    Public Property Title As String
    Public Shared Property Created As Integer

    Public Sub New(title As String)
        Me.Title = title
        Created += 1
    End Sub

    Public Sub Rename(newTitle As String)
        If String.IsNullOrWhiteSpace(newTitle) Then Throw New ArgumentException("Title is empty", NameOf(newTitle))
        Title = newTitle
    End Sub

    Public Sub Rename(newTitle As String, upperCase As Boolean)
        Title = If(upperCase, newTitle.ToUpperInvariant(), newTitle)
    End Sub

    Public Function Wrap(Of T)(value As T) As String
        Return "[" & value.ToString() & "]"
    End Function
End Class

Module Program
    Sub Main()
        Dim bookType As Type = GetType(Book)

        Console.WriteLine("-- looking types up by name")
        Console.WriteLine("Type.GetType(""Book"")          = " & If(Type.GetType("Book") Is Nothing, "Nothing", "found"))
        Console.WriteLine("Type.GetType(""ShelfApp.Book"") = " & If(Type.GetType("ShelfApp.Book") Is Nothing, "Nothing", "found"))
        Console.WriteLine("FullName                       = " & bookType.FullName)

        Console.WriteLine("-- creating objects")
        Dim book As Book = CType(Activator.CreateInstance(bookType, "Dune"), Book)
        Dim constructor As ConstructorInfo = bookType.GetConstructor(New Type() {GetType(String)})
        Dim second As Book = CType(constructor.Invoke(New Object() {"Emma"}), Book)
        Console.WriteLine($"{book.Title}, {second.Title}, created={Book.Created}")
        Try
            Dim noDefault As Object = Activator.CreateInstance(bookType)
        Catch ex As MissingMethodException
            Console.WriteLine("No parameterless constructor: " & ex.GetType().Name)
        End Try

        Console.WriteLine("-- invoking members")
        Try
            bookType.GetMethod("Rename")
        Catch ex As AmbiguousMatchException
            Console.WriteLine("GetMethod(""Rename"") with two overloads: " & ex.GetType().Name)
        End Try
        Dim rename1 As MethodInfo = bookType.GetMethod("Rename", New Type() {GetType(String)})
        Dim rename2 As MethodInfo = bookType.GetMethod("Rename", New Type() {GetType(String), GetType(Boolean)})
        rename1.Invoke(book, New Object() {"Dune Messiah"})
        rename2.Invoke(book, New Object() {"children of dune", True})
        Console.WriteLine("Title now " & book.Title)

        Dim titleProperty As PropertyInfo = bookType.GetProperty("Title")
        titleProperty.SetValue(book, "God Emperor")
        Console.WriteLine("Property read: " & CStr(titleProperty.GetValue(book)))

        Dim isbnField As FieldInfo = bookType.GetField("_isbn", BindingFlags.Instance Or BindingFlags.NonPublic)
        Console.WriteLine("Private field: " & CStr(isbnField.GetValue(book)))
        Console.WriteLine("Shared property: " & CInt(bookType.GetProperty("Created").GetValue(Nothing)))

        Try
            rename1.Invoke(book, New Object() {" "})
        Catch ex As TargetInvocationException
            Console.WriteLine("Wrapped: " & ex.GetType().Name & " -> " & ex.InnerException.GetType().Name)
        End Try
        Try
            rename1.Invoke(book, New Object() {42})
        Catch ex As ArgumentException
            Console.WriteLine("Wrong argument type: " & ex.GetType().Name)
        End Try

        Console.WriteLine("-- generics")
        Dim listType As Type = GetType(List(Of )).MakeGenericType(GetType(Book))
        Console.WriteLine(listType.ToString())
        Dim list As Object = Activator.CreateInstance(listType)
        listType.GetMethod("Add").Invoke(list, New Object() {book})
        Console.WriteLine("Count = " & CInt(listType.GetProperty("Count").GetValue(list)))
        Dim wrap As MethodInfo = bookType.GetMethod("Wrap").MakeGenericMethod(GetType(Integer))
        Console.WriteLine(CStr(wrap.Invoke(book, New Object() {42})))

        Console.WriteLine("-- a delegate removes the per-call cost")
        Dim getTitle As Func(Of Book, String) = CType(titleProperty.GetGetMethod().CreateDelegate(GetType(Func(Of Book, String))), Func(Of Book, String))
        Console.WriteLine(getTitle(book))
    End Sub
End Module
-- looking types up by name
Type.GetType("Book")          = Nothing
Type.GetType("ShelfApp.Book") = found
FullName                       = ShelfApp.Book
-- creating objects
Dune, Emma, created=2
No parameterless constructor: MissingMethodException
-- invoking members
GetMethod("Rename") with two overloads: AmbiguousMatchException
Title now CHILDREN OF DUNE
Property read: God Emperor
Private field: 978-0-00-000000-2
Shared property: 2
Wrapped: TargetInvocationException -> ArgumentException
Wrong argument type: ArgumentException
-- generics
System.Collections.Generic.List`1[ShelfApp.Book]
Count = 1
[42]
-- a delegate removes the per-call cost
God Emperor

Source: Activator class

What the run shows:

  • The type name includes the project’s root namespace. Type.GetType("Book") is Nothing — there is no exception — and Type.GetType("ShelfApp.Book") finds the type. Type.GetType(name, throwOnError:=True) is the variant that fails loudly.

  • GetMethod with an overloaded name throws AmbiguousMatchException unless you give the parameter types; the same happens with overloaded properties such as indexers.

  • An exception inside the invoked method arrives wrapped in TargetInvocationException; read InnerException. A wrong argument type arrives as ArgumentException before the method runs.

  • Private members are reachable with BindingFlags.NonPublic, which is why reflection over a library you do not own is fragile and why it is a security-sensitive feature in sandboxed code.

  • A shared member needs Nothing as the target (GetValue(Nothing)).

  • Generic types and methods are closed with MakeGenericType and MakeGenericMethod; the open type is written GetType(List(Of )).

  • Reflection is slower than a direct call. When a reflected call sits in a loop, create a delegate once (the last lines of the program: CreateDelegate(GetType(Func(Of Book, String)))) and call that; measure with BenchmarkDotNet before deciding anything.

Loading a plugin assembly

The reflection feature that matters most in real applications is finding implementations in an assembly that was not known when the host was compiled. The solution below has three projects: Contracts (an interface IExporter and an attribute ExporterAttribute), Plugin (four classes) and Host, which references Contracts only and loads Plugin.dll from a plugins folder at run time. The host builds the plugin first through a ProjectReference with ReferenceOutputAssembly="false" and copies the DLL into its output folder.

Option Strict On

Public Interface IExporter
    Function Export(titles As IEnumerable(Of String)) As String
End Interface

<AttributeUsage(AttributeTargets.Class, AllowMultiple:=False)>
Public NotInheritable Class ExporterAttribute
    Inherits Attribute

    Public ReadOnly Property Format As String

    Public Sub New(format As String)
        Me.Format = format
    End Sub
End Class
Option Strict On

Imports Shelf.Contracts

<Exporter("csv")>
Public Class CsvExporter
    Implements IExporter

    Public Function Export(titles As IEnumerable(Of String)) As String Implements IExporter.Export
        Return String.Join(",", titles)
    End Function
End Class

<Exporter("lines")>
Public Class LineExporter
    Implements IExporter

    Public Function Export(titles As IEnumerable(Of String)) As String Implements IExporter.Export
        Return String.Join(" | ", titles.Select(Function(t, i) $"{i + 1}. {t}"))
    End Function
End Class

Public Class NotAnExporter
End Class

Friend Class HiddenExporter
    Implements IExporter

    Public Function Export(titles As IEnumerable(Of String)) As String Implements IExporter.Export
        Return "hidden"
    End Function
End Class
<ItemGroup>
  <ProjectReference Include="..\Contracts\Contracts.vbproj" />
  <ProjectReference Include="..\Plugin\Plugin.vbproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<Target Name="CopyPlugin" AfterTargets="Build">
  <Copy SourceFiles="..\Plugin\bin\$(Configuration)\$(TargetFramework)\Plugin.dll" DestinationFolder="$(OutDir)plugins" />
</Target>
Option Strict On

Imports System.IO
Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Runtime.Loader
Imports Shelf.Contracts

Module Program
    <MethodImpl(MethodImplOptions.NoInlining)>
    Function RunPlugin(path As String) As WeakReference
        Dim context As New AssemblyLoadContext("plugins", isCollectible:=True)
        Dim assembly As Assembly = context.LoadFromAssemblyPath(path)
        Console.WriteLine("Loaded " & assembly.GetName().Name & " into context " & context.Name)

        Dim titles As String() = {"Dune", "Emma", "Ulysses"}
        For Each type As Type In assembly.GetTypes().OrderBy(Function(t) t.Name)
            Dim implementsExporter As Boolean = GetType(IExporter).IsAssignableFrom(type)
            Dim attribute As ExporterAttribute = type.GetCustomAttribute(Of ExporterAttribute)()
            Console.WriteLine($"{type.Name,-14} IExporter={implementsExporter,-5} public={type.IsPublic,-5} attribute={If(attribute Is Nothing, "none", attribute.Format)}")
            If implementsExporter AndAlso attribute IsNot Nothing Then
                Dim exporter As IExporter = CType(Activator.CreateInstance(type), IExporter)
                Console.WriteLine("   -> " & exporter.Export(titles))
            End If
        Next

        Dim weak As New WeakReference(context)
        context.Unload()
        Return weak
    End Function

    Sub Main()
        Dim path As String = System.IO.Path.Combine(AppContext.BaseDirectory, "plugins", "Plugin.dll")
        Console.WriteLine("Plugin file exists: " & File.Exists(path))
        Dim weak As WeakReference = RunPlugin(path)
        For attempt As Integer = 1 To 10
            If Not weak.IsAlive Then Exit For
            GC.Collect()
            GC.WaitForPendingFinalizers()
        Next
        Console.WriteLine("Context collected after unload: " & Not weak.IsAlive)
    End Sub
End Module
Plugin file exists: True
Loaded Plugin into context plugins
_Closure$__    IExporter=False public=False attribute=none
CsvExporter    IExporter=True  public=True  attribute=csv
   -> Dune,Emma,Ulysses
HiddenExporter IExporter=True  public=False attribute=none
LineExporter   IExporter=True  public=True  attribute=lines
   -> 1. Dune | 2. Emma | 3. Ulysses
NotAnExporter  IExporter=False public=True  attribute=none
Context collected after unload: True

The design decisions in the host:

  • The shared contract lives in a third assembly that both sides reference; the host loads the plugin into a separate AssemblyLoadContext, whose fallback to the default context resolves Contracts to the host’s copy, so IExporter is the same type on both sides. Without a shared contract the plugin’s IExporter would be a different type from the host’s.

  • The test GetType(IExporter).IsAssignableFrom(type) finds the implementations; HiddenExporter implements the interface but is Friend, so a host that wanted only public plugins would also test IsPublic. The compiler’s _Closure$__ class turns up in GetTypes() and is filtered out because it does not implement the interface.

  • The attribute carries the name the plugin wants to be known by. Reading the attribute does not require creating the plugin.

  • isCollectible:=True and Unload() let the runtime free the plugin assembly, but only when nothing references it: the loading code is in a NoInlining method so that no local from the plugin survives in the caller, and the test collects with WeakReference and GC.Collect in a loop. The last line shows the context was collected. A stray reference (a static event handler, a running thread, a cached Type) can keep it alive, and the line then reads False.

  • A real host must not trust arbitrary DLLs; a plugin is code that runs with the host’s rights.

Reflection, trimming and Native AOT

Trimming (PublishTrimmed) and Native AOT (PublishAot) remove code the linker thinks is unused. Reflection reaches code by name at run time, so the linker cannot see it: a type that is only created through Type.GetType(name) may be gone from the published application. The annotations that tell the tools what reflection needs are the attributes DynamicallyAccessedMembers (on a Type parameter, naming the members that will be reflected on), RequiresUnreferencedCode and RequiresDynamicCode (on a method that cannot work when trimmed). They are ordinary attributes, so they work in Visual Basic. The program below has one method of each kind:

Option Strict On

Imports System.Diagnostics.CodeAnalysis

Public Class CsvExporter
    Public Function Export() As String
        Return "csv"
    End Function
End Class

Module Program
    <RequiresUnreferencedCode("Creates a type from its name")>
    Function CreateByName(name As String) As Object
        Return Activator.CreateInstance(Type.GetType(name))
    End Function

    Function CreateKnown(<DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)> type As Type) As Object
        Return Activator.CreateInstance(type)
    End Function

    Function CreateUnannotated(type As Type) As Object
        Return Activator.CreateInstance(type)
    End Function

    Sub Main()
        Dim known As Object = CreateKnown(GetType(CsvExporter))
        Dim byName As Object = CreateByName("ShelfApp.CsvExporter")
        Dim unannotated As Object = CreateUnannotated(GetType(CsvExporter))
        Console.WriteLine(known.GetType().Name & " " & byName.GetType().Name & " " & unannotated.GetType().Name)
    End Sub
End Module

The project properties were PublishTrimmed with SelfContained and a RuntimeIdentifier of linux-arm64:

<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>

dotnet build printed no trimming diagnostics: with EnableTrimAnalyzer on, the compiler-side trim analyzer reported nothing for this Visual Basic program. dotnet publish runs the IL linker, which does report them, in IL terms (with the source line from the symbols; TreatWarningsAsErrors was off):

Program.vb(22,9): Trim analysis warning IL2067: ShelfApp.Program.CreateUnannotated(Type): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The parameter 'type' of method 'ShelfApp.Program.CreateUnannotated(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
Program.vb(27,13): Trim analysis warning IL2026: ShelfApp.Program.Main(): Using member 'ShelfApp.Program.CreateByName(String)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Creates a type from its name.
CsvExporter CsvExporter CsvExporter

The published application ran and printed CsvExporter CsvExporter CsvExporter; the CreateKnown method, whose Type parameter carries DynamicallyAccessedMembers, produced no warning because the annotation propagates the requirement to its callers. With TreatWarningsAsErrors on, the same warnings become errors and the publish fails:

Program.vb(22,9): Trim analysis error IL2067: ShelfApp.Program.CreateUnannotated(Type): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The parameter 'type' of method 'ShelfApp.Program.CreateUnannotated(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
Program.vb(27,13): Trim analysis error IL2026: ShelfApp.Program.Main(): Using member 'ShelfApp.Program.CreateByName(String)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Creates a type from its name.
/root/.nuget/packages/microsoft.net.illink.tasks/10.0.13/build/Microsoft.NET.ILLink.targets(103,5): error NETSDK1144: Optimizing assemblies for size failed.

The PublishAot property compiles the same program ahead of time. The container needs a native linker (clang) and zlib headers; installed, the program published and ran (the executable was about 2 MB), with the same two warnings:

Program.vb(22): Trim analysis warning IL2067: ShelfApp.Program.CreateUnannotated(Type): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The parameter 'type' of method 'ShelfApp.Program.CreateUnannotated(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
Program.vb(27): Trim analysis warning IL2026: ShelfApp.Program.Main(): Using member 'ShelfApp.Program.CreateByName(String)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Creates a type from its name.
CsvExporter CsvExporter CsvExporter

What this does and does not tell you: a small Visual Basic program trims and compiles with Native AOT. It does not show that a larger Visual Basic program does, because the Visual Basic runtime library itself uses reflection in some features (late binding and CallByName), and none of that was exercised here. See Early and Late Binding for the late-binding side, and Libraries, Packaging and Deployment for publishing options. Further, the System.Text.Json source generator that removes reflection from JSON serialization cannot be used from Visual Basic projects (it needs a C# helper project, see Serialization: JSON and XML).

Differences from C#

C# Visual Basic Note

[Obsolete("x")]

<Obsolete("x")>

angle brackets; same attribute classes

[Rule("a", Priority = 2)]

<Rule("a", Priority:=2)>

named arguments use :=

[assembly: X], [module: X]

<Assembly: X>, <Module: X>

at file level before the first declaration

[return: X] int F()

Function F() As <X> Integer

the attribute is part of the return type

[field: X], [property: X], [param: X], [event: X], [method: X]

no explicit target keywords

an attribute applies to the declaration it precedes; auto-property attributes go on the property

[A] [B] or [A, B]

<A> <B> or <A, B>

the same two forms

generic attributes [Kind<int>] (C# 11)

not allowed

BC32066

attributes on lambdas (C# 10) and local functions

not allowed

no attribute syntax inside a procedure body; < in an expression starts an XML literal, so <Description("x")> Function() 1 produces XML-literal errors (BC30625, BC31151, BC31165)

typeof(Book)

GetType(Book)

and GetType(List(Of )) for open generics

obj is T

TypeOf obj Is T

and GetType for exact types

nameof(x)

NameOf(x)

same result: the last identifier, constant; open generic types are not accepted in Visual Basic

[CallerMemberName] string name = null

<CallerMemberName> Optional name As String = Nothing

same attribute; Visual Basic silently ignores one on the wrong parameter type

dynamic

Object under Option Strict Off, see Early and Late Binding

different mechanism, same use

Error reference

Code Message Cause

BC30662

Attribute 'RuleAttribute' cannot be applied to 'Run' because the attribute is not valid on this declaration type.

outside AttributeUsage targets

BC30663

Attribute 'RuleAttribute' cannot be applied multiple times.

no AllowMultiple; also a duplicate of a generated AssemblyInfo attribute

BC30455

Argument not specified for parameter 'name' of 'Public Sub New(name As String)'.

a required constructor argument is missing

BC30057

Too many arguments to 'Public Sub New(name As String)'.

no constructor takes that many

BC30661

Field or property 'Missing' is not found.

a named argument names nothing

BC31501

'ReadOnly' attribute property 'Name' cannot be the target of an assignment.

a named argument for a read-only property

BC30934

Conversion from 'String' to 'Integer' cannot occur in a constant expression used as an argument to an attribute.

a named argument of the wrong type

BC30059

Constant expression is required.

an attribute argument that is not a constant

BC31504

'Plain' cannot be used as an attribute because it does not inherit from 'System.Attribute'.

not an attribute class

BC32066

Type arguments are not valid because attributes cannot be generic.

generic attribute

BC30660

Attributes cannot be applied to local variables.

an attribute inside a body

BC30637

Assembly or Module attribute statements must precede any declarations in a file.

<Assembly: after a declaration

BC30636

'>' expected.

a target the language does not have (field:, Return:)

BC30668

'Public Sub OldName()' is obsolete: 'Use NewName'.

use of an Obsolete(..., True) member

BC40000

'Public Sub OlderName()' is obsolete: 'Use NewName'.

warning for use of an Obsolete member

BC42505

The CallerArgumentExpressionAttribute applied to parameter 'text' will have no effect. It is applied with an invalid parameter name.

warning; the attribute names no parameter

BC30182

Type expected.

NameOf(Dictionary(Of ,))

BC37244

This expression does not have a name.

NameOf(Integer), NameOf(1 + 1), NameOf(Console.WriteLine("x"))

BC30451

'DoesNotExist' is not declared. It may be inaccessible due to its protection level.

NameOf of an unknown name

IL2026

Using member '...' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code.

linker warning at publish

IL2067

'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to ...

linker warning at publish

Pitfalls checklist

  • An <Assembly: attribute that the SDK also generates is a duplicate (BC30663); set the project property instead or switch off GenerateAssemblyInfo.

  • The reflection name of a type includes the root namespace: Type.GetType("Book") returns Nothing; use the FullName.

  • GetCustomAttribute(Of T) throws AmbiguousMatchException when the attribute allows multiples and appears more than once.

  • MemberInfo.GetCustomAttributes(inherit:=True) does not look at the base member of a property or event; use Attribute.GetCustomAttributes.

  • Reading an attribute runs its constructor; use IsDefined or CustomAttributeData when you only need to know it is there.

  • Caller-information attributes need an Optional parameter of the right type; Visual Basic does not warn when they are wasted.

  • CallerFilePath embeds the build machine’s path; use PathMap for shipped binaries.

  • MethodInfo.Invoke wraps exceptions in TargetInvocationException; unwrap InnerException.

  • Reflection finds the compiler’s own types and members (_Closure$__, _Title, $STATIC$...); filter by IsSpecialName and CompilerGeneratedAttribute.

  • A name used only through reflection can be trimmed away; annotate it with DynamicallyAccessedMembers or keep it with a root descriptor. The Visual Basic build printed no trimming diagnostics here, so run the publish with trim warnings as errors in CI.

  • A collectible AssemblyLoadContext is unloaded only when nothing references it; keep the loading code in a separate non-inlined method and test it.

  • Never load or run code from a path supplied by users.