C#idiomatic C#
reflection-and-attributescsharp/advanced-topics

Advanced

Attributes

Use attributes to associate metadata or declarative information in C#. Query attributes at run time with reflection APIs that describe assemblies, modules, and types.

  • page csharp/advanced-topics/reflection-and-attributes
  • updated 03/14/2025

Attributes provide a powerful way to associate metadata, or declarative information, with code (assemblies, types, methods, properties, and so on). After you associate an attribute with a program entity, you can query the attribute at run time by using a technique called reflection.

Attributes have the following properties:

  • Attributes add metadata to your program. Metadata is information about the types defined in a program. All .NET assemblies contain a specified set of metadata that describes the types and type members defined in the assembly. You can add custom attributes to specify any other required information.
  • Attributes can be applied to entire assemblies, modules, or smaller program elements, such as classes and properties.
  • Attributes can accept arguments in the same way as methods and properties.
  • Attributes enable a program to examine its own metadata or metadata in other programs by using reflection.

Work with reflection

Reflection APIs provided by System.Type describe assemblies, modules, and types. You can use reflection to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties. When you use attributes in your code, reflection enables you to access them. For more information, see Attributes.

Here's a simple example of reflection with the System.Object.GetType method. All types from the Object base class inherit this method, which is used to obtain the type of a variable:

sample · csharpdocs/csharp/advanced-topics/reflection-and-attributes/snippets/conceptual/Program.csGetTypeInformation

csharp
// See https://aka.ms/new-console-template for more information


using System.Reflection;

ReflectionExamples.TestAuthorAttribute.Test();

// <GetTypeInformation>
// Using GetType to obtain type information:
int i = 42;
Type type = i.GetType();
Console.WriteLine(type);
// </GetTypeInformation>

// <GetAssemblyInfo>
// Using Reflection to get information of an Assembly:
Assembly info = typeof(int).Assembly;
Console.WriteLine(info);
// </GetAssemblyInfo>

Query();

void Query()
{
    // <QueryReflection>
    Assembly assembly = Assembly.Load("System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e");
    var pubTypesQuery = from type in assembly.GetTypes()
                        where type.IsPublic
                        from method in type.GetMethods()
                        where method.ReturnType.IsArray == true
                            || (method.ReturnType.GetInterface(
                                typeof(System.Collections.Generic.IEnumerable<>).FullName!) != null
                            && method.ReturnType.FullName != "System.String")
                        group method.ToString() by type.ToString();

    foreach (var groupOfMethods in pubTypesQuery)
    {
        Console.WriteLine($"Type: {groupOfMethods.Key}");
        foreach (var method in groupOfMethods)
        {
            Console.WriteLine($"  {method}");
        }
    }
    // </QueryReflection>
}

The output shows the type:

output
System.Int32

The following example uses reflection to obtain the full name of the loaded assembly.

sample · csharpdocs/csharp/advanced-topics/reflection-and-attributes/snippets/conceptual/Program.csGetAssemblyInfo

csharp
// See https://aka.ms/new-console-template for more information


using System.Reflection;

ReflectionExamples.TestAuthorAttribute.Test();

// <GetTypeInformation>
// Using GetType to obtain type information:
int i = 42;
Type type = i.GetType();
Console.WriteLine(type);
// </GetTypeInformation>

// <GetAssemblyInfo>
// Using Reflection to get information of an Assembly:
Assembly info = typeof(int).Assembly;
Console.WriteLine(info);
// </GetAssemblyInfo>

Query();

void Query()
{
    // <QueryReflection>
    Assembly assembly = Assembly.Load("System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e");
    var pubTypesQuery = from type in assembly.GetTypes()
                        where type.IsPublic
                        from method in type.GetMethods()
                        where method.ReturnType.IsArray == true
                            || (method.ReturnType.GetInterface(
                                typeof(System.Collections.Generic.IEnumerable<>).FullName!) != null
                            && method.ReturnType.FullName != "System.String")
                        group method.ToString() by type.ToString();

    foreach (var groupOfMethods in pubTypesQuery)
    {
        Console.WriteLine($"Type: {groupOfMethods.Key}");
        foreach (var method in groupOfMethods)
        {
            Console.WriteLine($"  {method}");
        }
    }
    // </QueryReflection>
}

The output is similar to the following example:

output
System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e

Keyword differences for IL

The C# keywords protected and internal have no meaning in Intermediate Language (IL) and aren't used in the reflection APIs. The corresponding terms in IL are Family and Assembly. Here some ways you can use these terms:

Work with attributes

Attributes can be placed on almost any declaration, though a specific attribute might restrict the types of declarations on which it's valid. In C#, you specify an attribute by placing the name of the attribute enclosed in square brackets ([]) above the declaration of the entity to which it applies.

In this example, you use the System.SerializableAttribute attribute to apply a specific characteristic to a class:

sample · csharpdocs/csharp/advanced-topics/reflection-and-attributes/snippets/conceptual/AttributesOverview.csSnippet1

csharp
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace AttributeOverview;

public class ValidatedContractAttribute : System.Attribute
{
}

// <Snippet1>
[Serializable]
public class SampleClass
{
    // Objects of this type can be serialized.
}
// </Snippet1>

class AttributesOverview
{
    // <Snippet2>
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    extern static void SampleMethod();
    // </Snippet2>

    // <Snippet4>
    void MethodA([In][Out] ref double x) { }
    void MethodB([Out][In] ref double x) { }
    void MethodC([In, Out] ref double x) { }
    // </Snippet4>

    // <Snippet5>
    [Conditional("DEBUG"), Conditional("TEST1")]
    void TraceMethod()
    {
        // ...
    }
    // </Snippet5>

    // <Snippet6>
    // default: applies to method
    [ValidatedContract]
    int Method1() { return 0; }

    // applies to method
    [method: ValidatedContract]
    int Method2() { return 0; }

    // applies to parameter
    int Method3([ValidatedContract] string contract) { return 0; }

    // applies to return value
    [return: ValidatedContract]
    int Method4() { return 0; }
    // </Snippet6>
}

You can declare a method with the System.Runtime.InteropServices.DllImportAttribute attribute:

sample · csharpdocs/csharp/advanced-topics/reflection-and-attributes/snippets/conceptual/AttributesOverview.csSnippet2

csharp
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace AttributeOverview;

public class ValidatedContractAttribute : System.Attribute
{
}

// <Snippet1>
[Serializable]
public class SampleClass
{
    // Objects of this type can be serialized.
}
// </Snippet1>

class AttributesOverview
{
    // <Snippet2>
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    extern static void SampleMethod();
    // </Snippet2>

    // <Snippet4>
    void MethodA([In][Out] ref double x) { }
    void MethodB([Out][In] ref double x) { }
    void MethodC([In, Out] ref double x) { }
    // </Snippet4>

    // <Snippet5>
    [Conditional("DEBUG"), Conditional("TEST1")]
    void TraceMethod()
    {
        // ...
    }
    // </Snippet5>

    // <Snippet6>
    // default: applies to method
    [ValidatedContract]
    int Method1() { return 0; }

    // applies to method
    [method: ValidatedContract]
    int Method2() { return 0; }

    // applies to parameter
    int Method3([ValidatedContract] string contract) { return 0; }

    // applies to return value
    [return: ValidatedContract]
    int Method4() { return 0; }
    // </Snippet6>
}

You can place multiple attributes on a declaration:

sample · csharpdocs/csharp/advanced-topics/reflection-and-attributes/snippets/conceptual/AttributesOverview.csSnippet4

csharp
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace AttributeOverview;

public class ValidatedContractAttribute : System.Attribute
{
}

// <Snippet1>
[Serializable]
public class SampleClass
{
    // Objects of this type can be serialized.
}
// </Snippet1>

class AttributesOverview
{
    // <Snippet2>
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    extern static void SampleMethod();
    // </Snippet2>

    // <Snippet4>
    void MethodA([In][Out] ref double x) { }
    void MethodB([Out][In] ref double x) { }
    void MethodC([In, Out] ref double x) { }
    // </Snippet4>

    // <Snippet5>
    [Conditional("DEBUG"), Conditional("TEST1")]
    void TraceMethod()
    {
        // ...
    }
    // </Snippet5>

    // <Snippet6>
    // default: applies to method
    [ValidatedContract]
    int Method1() { return 0; }

    // applies to method
    [method: ValidatedContract]
    int Method2() { return 0; }

    // applies to parameter
    int Method3([ValidatedContract] string contract) { return 0; }

    // applies to return value
    [return: ValidatedContract]
    int Method4() { return 0; }
    // </Snippet6>
}

Some attributes can be specified more than once for a given entity. The following example shows multiuse of the System.Diagnostics.ConditionalAttribute attribute:

sample · csharpdocs/csharp/advanced-topics/reflection-and-attributes/snippets/conceptual/AttributesOverview.csSnippet5

csharp
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace AttributeOverview;

public class ValidatedContractAttribute : System.Attribute
{
}

// <Snippet1>
[Serializable]
public class SampleClass
{
    // Objects of this type can be serialized.
}
// </Snippet1>

class AttributesOverview
{
    // <Snippet2>
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    extern static void SampleMethod();
    // </Snippet2>

    // <Snippet4>
    void MethodA([In][Out] ref double x) { }
    void MethodB([Out][In] ref double x) { }
    void MethodC([In, Out] ref double x) { }
    // </Snippet4>

    // <Snippet5>
    [Conditional("DEBUG"), Conditional("TEST1")]
    void TraceMethod()
    {
        // ...
    }
    // </Snippet5>

    // <Snippet6>
    // default: applies to method
    [ValidatedContract]
    int Method1() { return 0; }

    // applies to method
    [method: ValidatedContract]
    int Method2() { return 0; }

    // applies to parameter
    int Method3([ValidatedContract] string contract) { return 0; }

    // applies to return value
    [return: ValidatedContract]
    int Method4() { return 0; }
    // </Snippet6>
}

Attribute parameters

Many attributes have parameters, which can be positional, unnamed, or named. The following table describes how to work with named and positional attributes:

Positional parameters

Parameters of the attribute constructor:

Named parameters

Properties or fields of the attribute:

  • Must specify, can't omit

  • Always specify first

  • Specify in certain order

  • Always optional, omit when false

  • Specify after positional parameters

  • Specify in any order

For example, the following code shows three equivalent DllImport attributes:

csharp
[DllImport("user32.dll")]
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]

The first parameter, the DLL name, is positional and always comes first. The other instances are named parameters. In this scenario, both named parameters default to false, so they can be omitted. Refer to the individual attribute's documentation for information on default parameter values. For more information on allowed parameter types, see the Attributes section of the C# language specification.

Attribute targets

The target of an attribute is the entity that the attribute applies to. For example, an attribute can apply to a class, a method, or an assembly. By default, an attribute applies to the element that follows it. But you can also explicitly identify the element to associate, such as a method, a parameter, or the return value.

To explicitly identify an attribute target, use the following syntax:

csharp
[target : attribute-list]

The following table shows the list of possible target values.

Target value Applies to
assembly Entire assembly
module Current assembly module
field Field in a class or a struct
event Event
method Method, constructor (including a primary constructor), or get and set property accessors
param Method parameters or set property accessor parameters
property Property
return Return value of a method, property indexer, or get property accessor
type Struct, class, interface, enum, or delegate

You can specify the field target value to apply an attribute to the backing field created for an automatically implemented property.

The following example shows how to apply attributes to assemblies and modules. For more information, see Common attributes (C#).

csharp
using System;
using System.Reflection;
[assembly: AssemblyTitleAttribute("Production assembly 4")]
[module: CLSCompliant(true)]

The following example shows how to apply attributes to methods, method parameters, and method return values in C#.

sample · csharpdocs/csharp/advanced-topics/reflection-and-attributes/snippets/conceptual/AttributesOverview.csSnippet6

csharp
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace AttributeOverview;

public class ValidatedContractAttribute : System.Attribute
{
}

// <Snippet1>
[Serializable]
public class SampleClass
{
    // Objects of this type can be serialized.
}
// </Snippet1>

class AttributesOverview
{
    // <Snippet2>
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    extern static void SampleMethod();
    // </Snippet2>

    // <Snippet4>
    void MethodA([In][Out] ref double x) { }
    void MethodB([Out][In] ref double x) { }
    void MethodC([In, Out] ref double x) { }
    // </Snippet4>

    // <Snippet5>
    [Conditional("DEBUG"), Conditional("TEST1")]
    void TraceMethod()
    {
        // ...
    }
    // </Snippet5>

    // <Snippet6>
    // default: applies to method
    [ValidatedContract]
    int Method1() { return 0; }

    // applies to method
    [method: ValidatedContract]
    int Method2() { return 0; }

    // applies to parameter
    int Method3([ValidatedContract] string contract) { return 0; }

    // applies to return value
    [return: ValidatedContract]
    int Method4() { return 0; }
    // </Snippet6>
}

Review ways to use attributes

Here are some common ways to use attributes in code:

  • Mark controller methods that respond to POST messages by using the HttpPost attribute. For more information, see the Microsoft.AspNetCore.Mvc.HttpPostAttribute class.
  • Describe how to marshal method parameters when interoperating with native code. For more information, see the System.Runtime.InteropServices.MarshalAsAttribute class.
  • Describe Component Object Model (COM) properties for classes, methods, and interfaces.
  • Call unmanaged code by using the System.Runtime.InteropServices.DllImportAttribute class.
  • Describe your assembly in terms of title, version, description, or trademark.
  • Describe which members of a class to serialize for persistence.
  • Describe how to map between class members and XML nodes for XML serialization.
  • Describe the security requirements for methods.
  • Specify characteristics used to enforce security.
  • Control optimizations with the just-in-time (JIT) compiler so the code remains easy to debug.
  • Obtain information about the caller to a method.

Review reflection scenarios

Reflection is useful in the following scenarios: