C#idiomatic C#
nameofcsharp/fundamentals/strings

Language

The `nameof` operator

Use the nameof operator to capture an identifier as a compile-time string for argument validation, change notifications, attributes, and logging.

  • page csharp/fundamentals/strings/nameof
  • updated 05/08/2026

The nameof operator returns the textual identifier of a symbol, such as a variable, parameter, type, member, or namespace, as a compile-time string constant. Anywhere you'd otherwise hardcode an identifier as a string, use nameof: the compiler verifies that the symbol exists, and rename refactorings update the result automatically.

What nameof returns

nameof evaluates to the final identifier in its operand. It runs at compile time and has no runtime cost.

sample · csharpdocs/csharp/fundamentals/strings/snippets/nameof/Program.cs

csharp
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace NameofSample;

public static class Program
{
    public static void Main()
    {
        ShowBasic();
        ShowGuardClause();
        ShowThrowIfNull();
        ShowPropertyChanged();
        ShowAttributeUsage();
        ShowUnboundGenerics();
        ShowQualifiedName();
    }

    private static void ShowBasic()
    {
        // <Basic>
        // nameof produces the textual identifier of a symbol at compile time.
        Console.WriteLine(nameof(Customer));        // Customer
        Console.WriteLine(nameof(Customer.Name));   // Name

        var customer = new Customer("Ada");
        Console.WriteLine(nameof(customer));        // customer
        Console.WriteLine(nameof(customer.Name));   // Name
        // </Basic>
    }

    private static void ShowGuardClause()
    {
        // <GuardClause>
        try
        {
            Greet("");
        }
        catch (ArgumentException ex)
        {
            // The exception's ParamName is the literal "name", produced by nameof at compile time.
            Console.WriteLine($"{ex.ParamName}: {ex.Message}");
        }

        static void Greet(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Name must be non-empty.", nameof(name));
            }
            Console.WriteLine($"Hello, {name}!");
        }
        // </GuardClause>
    }

    private static void ShowThrowIfNull()
    {
        // <ThrowIfNull>
        // ArgumentNullException.ThrowIfNull captures the argument's name automatically
        // through [CallerArgumentExpression], so a separate nameof isn't required for
        // the null check. Use nameof for cases the helpers don't cover.
        Customer? maybeCustomer = null;

        try
        {
            Save(maybeCustomer);
        }
        catch (ArgumentNullException ex)
        {
            Console.WriteLine(ex.ParamName);   // customer
        }

        static void Save(Customer? customer)
        {
            ArgumentNullException.ThrowIfNull(customer);
            // ...
        }
        // </ThrowIfNull>
    }

    private static void ShowPropertyChanged()
    {
        // <PropertyChanged>
        var person = new Person();
        person.PropertyChanged += (_, e) => Console.WriteLine($"changed: {e.PropertyName}");

        person.Name = "Ada";    // changed: Name
        person.Name = "Grace";  // changed: Name
        // </PropertyChanged>
    }

    private static void ShowAttributeUsage()
    {
        // <AttributeUsage>
        // nameof works inside attribute arguments. The compiler resolves the
        // identifier even when the attribute targets a method or its parameters.
        Console.WriteLine(NormalizeOrNull("  hi  ") ?? "<null>");   // hi
        Console.WriteLine(NormalizeOrNull(null) ?? "<null>");        // <null>

        [return: NotNullIfNotNull(nameof(input))]
        static string? NormalizeOrNull(string? input) => input?.Trim();
        // </AttributeUsage>
    }

    private static void ShowUnboundGenerics()
    {
        // <UnboundGenerics>
        // Beginning in C# 14, nameof accepts an unbound generic type.
        // The result is the simple type name without any type-argument list.
        Console.WriteLine(nameof(List<>));                  // List
        Console.WriteLine(nameof(Dictionary<,>));           // Dictionary
        // </UnboundGenerics>
    }

    private static void ShowQualifiedName()
    {
        // <QualifiedName>
        // For a qualified expression, nameof returns only the final identifier.
        Console.WriteLine(nameof(System.Collections.Generic.List<int>)); // List
        Console.WriteLine(nameof(Customer.Name));                        // Name
        // </QualifiedName>
    }
}

// <CustomerType>
public sealed record Customer(string Name);
// </CustomerType>

// <PersonType>
public sealed class Person : INotifyPropertyChanged
{
    private string _name = "";

    public string Name
    {
        get => _name;
        set
        {
            if (_name == value) return;
            _name = value;
            // nameof keeps the property name and the change notification in sync.
            // Renaming the property automatically updates this argument.
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// </PersonType>

The operand can also be a qualified expression, one that uses the dot operator to navigate from a containing scope to a member, such as customer.Name, System.Console, or List<int>.Enumerator. In that case, only the last identifier is captured: nameof(customer.Name) returns "Name", not "customer.Name".

Argument validation

The classic use is producing the parameter name in a thrown exception. Pass nameof(parameter) instead of the literal string "parameter" so a future rename can't leave the message lying:

sample · csharpdocs/csharp/fundamentals/strings/snippets/nameof/Program.cs

csharp
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace NameofSample;

public static class Program
{
    public static void Main()
    {
        ShowBasic();
        ShowGuardClause();
        ShowThrowIfNull();
        ShowPropertyChanged();
        ShowAttributeUsage();
        ShowUnboundGenerics();
        ShowQualifiedName();
    }

    private static void ShowBasic()
    {
        // <Basic>
        // nameof produces the textual identifier of a symbol at compile time.
        Console.WriteLine(nameof(Customer));        // Customer
        Console.WriteLine(nameof(Customer.Name));   // Name

        var customer = new Customer("Ada");
        Console.WriteLine(nameof(customer));        // customer
        Console.WriteLine(nameof(customer.Name));   // Name
        // </Basic>
    }

    private static void ShowGuardClause()
    {
        // <GuardClause>
        try
        {
            Greet("");
        }
        catch (ArgumentException ex)
        {
            // The exception's ParamName is the literal "name", produced by nameof at compile time.
            Console.WriteLine($"{ex.ParamName}: {ex.Message}");
        }

        static void Greet(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Name must be non-empty.", nameof(name));
            }
            Console.WriteLine($"Hello, {name}!");
        }
        // </GuardClause>
    }

    private static void ShowThrowIfNull()
    {
        // <ThrowIfNull>
        // ArgumentNullException.ThrowIfNull captures the argument's name automatically
        // through [CallerArgumentExpression], so a separate nameof isn't required for
        // the null check. Use nameof for cases the helpers don't cover.
        Customer? maybeCustomer = null;

        try
        {
            Save(maybeCustomer);
        }
        catch (ArgumentNullException ex)
        {
            Console.WriteLine(ex.ParamName);   // customer
        }

        static void Save(Customer? customer)
        {
            ArgumentNullException.ThrowIfNull(customer);
            // ...
        }
        // </ThrowIfNull>
    }

    private static void ShowPropertyChanged()
    {
        // <PropertyChanged>
        var person = new Person();
        person.PropertyChanged += (_, e) => Console.WriteLine($"changed: {e.PropertyName}");

        person.Name = "Ada";    // changed: Name
        person.Name = "Grace";  // changed: Name
        // </PropertyChanged>
    }

    private static void ShowAttributeUsage()
    {
        // <AttributeUsage>
        // nameof works inside attribute arguments. The compiler resolves the
        // identifier even when the attribute targets a method or its parameters.
        Console.WriteLine(NormalizeOrNull("  hi  ") ?? "<null>");   // hi
        Console.WriteLine(NormalizeOrNull(null) ?? "<null>");        // <null>

        [return: NotNullIfNotNull(nameof(input))]
        static string? NormalizeOrNull(string? input) => input?.Trim();
        // </AttributeUsage>
    }

    private static void ShowUnboundGenerics()
    {
        // <UnboundGenerics>
        // Beginning in C# 14, nameof accepts an unbound generic type.
        // The result is the simple type name without any type-argument list.
        Console.WriteLine(nameof(List<>));                  // List
        Console.WriteLine(nameof(Dictionary<,>));           // Dictionary
        // </UnboundGenerics>
    }

    private static void ShowQualifiedName()
    {
        // <QualifiedName>
        // For a qualified expression, nameof returns only the final identifier.
        Console.WriteLine(nameof(System.Collections.Generic.List<int>)); // List
        Console.WriteLine(nameof(Customer.Name));                        // Name
        // </QualifiedName>
    }
}

// <CustomerType>
public sealed record Customer(string Name);
// </CustomerType>

// <PersonType>
public sealed class Person : INotifyPropertyChanged
{
    private string _name = "";

    public string Name
    {
        get => _name;
        set
        {
            if (_name == value) return;
            _name = value;
            // nameof keeps the property name and the change notification in sync.
            // Renaming the property automatically updates this argument.
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// </PersonType>

For null checks specifically, prefer using exception helpers. These helpers, such as System.ArgumentNullException.ThrowIfNull, capture the argument's name automatically through System.Runtime.CompilerServices.CallerArgumentExpressionAttribute, so a separate nameof isn't needed:

sample · csharpdocs/csharp/fundamentals/strings/snippets/nameof/Program.cs

csharp
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace NameofSample;

public static class Program
{
    public static void Main()
    {
        ShowBasic();
        ShowGuardClause();
        ShowThrowIfNull();
        ShowPropertyChanged();
        ShowAttributeUsage();
        ShowUnboundGenerics();
        ShowQualifiedName();
    }

    private static void ShowBasic()
    {
        // <Basic>
        // nameof produces the textual identifier of a symbol at compile time.
        Console.WriteLine(nameof(Customer));        // Customer
        Console.WriteLine(nameof(Customer.Name));   // Name

        var customer = new Customer("Ada");
        Console.WriteLine(nameof(customer));        // customer
        Console.WriteLine(nameof(customer.Name));   // Name
        // </Basic>
    }

    private static void ShowGuardClause()
    {
        // <GuardClause>
        try
        {
            Greet("");
        }
        catch (ArgumentException ex)
        {
            // The exception's ParamName is the literal "name", produced by nameof at compile time.
            Console.WriteLine($"{ex.ParamName}: {ex.Message}");
        }

        static void Greet(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Name must be non-empty.", nameof(name));
            }
            Console.WriteLine($"Hello, {name}!");
        }
        // </GuardClause>
    }

    private static void ShowThrowIfNull()
    {
        // <ThrowIfNull>
        // ArgumentNullException.ThrowIfNull captures the argument's name automatically
        // through [CallerArgumentExpression], so a separate nameof isn't required for
        // the null check. Use nameof for cases the helpers don't cover.
        Customer? maybeCustomer = null;

        try
        {
            Save(maybeCustomer);
        }
        catch (ArgumentNullException ex)
        {
            Console.WriteLine(ex.ParamName);   // customer
        }

        static void Save(Customer? customer)
        {
            ArgumentNullException.ThrowIfNull(customer);
            // ...
        }
        // </ThrowIfNull>
    }

    private static void ShowPropertyChanged()
    {
        // <PropertyChanged>
        var person = new Person();
        person.PropertyChanged += (_, e) => Console.WriteLine($"changed: {e.PropertyName}");

        person.Name = "Ada";    // changed: Name
        person.Name = "Grace";  // changed: Name
        // </PropertyChanged>
    }

    private static void ShowAttributeUsage()
    {
        // <AttributeUsage>
        // nameof works inside attribute arguments. The compiler resolves the
        // identifier even when the attribute targets a method or its parameters.
        Console.WriteLine(NormalizeOrNull("  hi  ") ?? "<null>");   // hi
        Console.WriteLine(NormalizeOrNull(null) ?? "<null>");        // <null>

        [return: NotNullIfNotNull(nameof(input))]
        static string? NormalizeOrNull(string? input) => input?.Trim();
        // </AttributeUsage>
    }

    private static void ShowUnboundGenerics()
    {
        // <UnboundGenerics>
        // Beginning in C# 14, nameof accepts an unbound generic type.
        // The result is the simple type name without any type-argument list.
        Console.WriteLine(nameof(List<>));                  // List
        Console.WriteLine(nameof(Dictionary<,>));           // Dictionary
        // </UnboundGenerics>
    }

    private static void ShowQualifiedName()
    {
        // <QualifiedName>
        // For a qualified expression, nameof returns only the final identifier.
        Console.WriteLine(nameof(System.Collections.Generic.List<int>)); // List
        Console.WriteLine(nameof(Customer.Name));                        // Name
        // </QualifiedName>
    }
}

// <CustomerType>
public sealed record Customer(string Name);
// </CustomerType>

// <PersonType>
public sealed class Person : INotifyPropertyChanged
{
    private string _name = "";

    public string Name
    {
        get => _name;
        set
        {
            if (_name == value) return;
            _name = value;
            // nameof keeps the property name and the change notification in sync.
            // Renaming the property automatically updates this argument.
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// </PersonType>

Use nameof for the cases the helpers don't cover: System.ArgumentException, System.ArgumentOutOfRangeException (when validating something other than a single argument), and other guard messages.

Property change notifications

Types that implement System.ComponentModel.INotifyPropertyChanged raise an event whose payload includes the changed property's name. Hardcoding the name as a string creates a silent bug if the property is renamed and the string isn't. Use nameof instead:

sample · csharpdocs/csharp/fundamentals/strings/snippets/nameof/Program.cs

csharp
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace NameofSample;

public static class Program
{
    public static void Main()
    {
        ShowBasic();
        ShowGuardClause();
        ShowThrowIfNull();
        ShowPropertyChanged();
        ShowAttributeUsage();
        ShowUnboundGenerics();
        ShowQualifiedName();
    }

    private static void ShowBasic()
    {
        // <Basic>
        // nameof produces the textual identifier of a symbol at compile time.
        Console.WriteLine(nameof(Customer));        // Customer
        Console.WriteLine(nameof(Customer.Name));   // Name

        var customer = new Customer("Ada");
        Console.WriteLine(nameof(customer));        // customer
        Console.WriteLine(nameof(customer.Name));   // Name
        // </Basic>
    }

    private static void ShowGuardClause()
    {
        // <GuardClause>
        try
        {
            Greet("");
        }
        catch (ArgumentException ex)
        {
            // The exception's ParamName is the literal "name", produced by nameof at compile time.
            Console.WriteLine($"{ex.ParamName}: {ex.Message}");
        }

        static void Greet(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Name must be non-empty.", nameof(name));
            }
            Console.WriteLine($"Hello, {name}!");
        }
        // </GuardClause>
    }

    private static void ShowThrowIfNull()
    {
        // <ThrowIfNull>
        // ArgumentNullException.ThrowIfNull captures the argument's name automatically
        // through [CallerArgumentExpression], so a separate nameof isn't required for
        // the null check. Use nameof for cases the helpers don't cover.
        Customer? maybeCustomer = null;

        try
        {
            Save(maybeCustomer);
        }
        catch (ArgumentNullException ex)
        {
            Console.WriteLine(ex.ParamName);   // customer
        }

        static void Save(Customer? customer)
        {
            ArgumentNullException.ThrowIfNull(customer);
            // ...
        }
        // </ThrowIfNull>
    }

    private static void ShowPropertyChanged()
    {
        // <PropertyChanged>
        var person = new Person();
        person.PropertyChanged += (_, e) => Console.WriteLine($"changed: {e.PropertyName}");

        person.Name = "Ada";    // changed: Name
        person.Name = "Grace";  // changed: Name
        // </PropertyChanged>
    }

    private static void ShowAttributeUsage()
    {
        // <AttributeUsage>
        // nameof works inside attribute arguments. The compiler resolves the
        // identifier even when the attribute targets a method or its parameters.
        Console.WriteLine(NormalizeOrNull("  hi  ") ?? "<null>");   // hi
        Console.WriteLine(NormalizeOrNull(null) ?? "<null>");        // <null>

        [return: NotNullIfNotNull(nameof(input))]
        static string? NormalizeOrNull(string? input) => input?.Trim();
        // </AttributeUsage>
    }

    private static void ShowUnboundGenerics()
    {
        // <UnboundGenerics>
        // Beginning in C# 14, nameof accepts an unbound generic type.
        // The result is the simple type name without any type-argument list.
        Console.WriteLine(nameof(List<>));                  // List
        Console.WriteLine(nameof(Dictionary<,>));           // Dictionary
        // </UnboundGenerics>
    }

    private static void ShowQualifiedName()
    {
        // <QualifiedName>
        // For a qualified expression, nameof returns only the final identifier.
        Console.WriteLine(nameof(System.Collections.Generic.List<int>)); // List
        Console.WriteLine(nameof(Customer.Name));                        // Name
        // </QualifiedName>
    }
}

// <CustomerType>
public sealed record Customer(string Name);
// </CustomerType>

// <PersonType>
public sealed class Person : INotifyPropertyChanged
{
    private string _name = "";

    public string Name
    {
        get => _name;
        set
        {
            if (_name == value) return;
            _name = value;
            // nameof keeps the property name and the change notification in sync.
            // Renaming the property automatically updates this argument.
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// </PersonType>

The setter calls OnPropertyChanged(nameof(Name)) so the property name and the change notification stay in sync. Run the example to see the events fire:

sample · csharpdocs/csharp/fundamentals/strings/snippets/nameof/Program.cs

csharp
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace NameofSample;

public static class Program
{
    public static void Main()
    {
        ShowBasic();
        ShowGuardClause();
        ShowThrowIfNull();
        ShowPropertyChanged();
        ShowAttributeUsage();
        ShowUnboundGenerics();
        ShowQualifiedName();
    }

    private static void ShowBasic()
    {
        // <Basic>
        // nameof produces the textual identifier of a symbol at compile time.
        Console.WriteLine(nameof(Customer));        // Customer
        Console.WriteLine(nameof(Customer.Name));   // Name

        var customer = new Customer("Ada");
        Console.WriteLine(nameof(customer));        // customer
        Console.WriteLine(nameof(customer.Name));   // Name
        // </Basic>
    }

    private static void ShowGuardClause()
    {
        // <GuardClause>
        try
        {
            Greet("");
        }
        catch (ArgumentException ex)
        {
            // The exception's ParamName is the literal "name", produced by nameof at compile time.
            Console.WriteLine($"{ex.ParamName}: {ex.Message}");
        }

        static void Greet(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Name must be non-empty.", nameof(name));
            }
            Console.WriteLine($"Hello, {name}!");
        }
        // </GuardClause>
    }

    private static void ShowThrowIfNull()
    {
        // <ThrowIfNull>
        // ArgumentNullException.ThrowIfNull captures the argument's name automatically
        // through [CallerArgumentExpression], so a separate nameof isn't required for
        // the null check. Use nameof for cases the helpers don't cover.
        Customer? maybeCustomer = null;

        try
        {
            Save(maybeCustomer);
        }
        catch (ArgumentNullException ex)
        {
            Console.WriteLine(ex.ParamName);   // customer
        }

        static void Save(Customer? customer)
        {
            ArgumentNullException.ThrowIfNull(customer);
            // ...
        }
        // </ThrowIfNull>
    }

    private static void ShowPropertyChanged()
    {
        // <PropertyChanged>
        var person = new Person();
        person.PropertyChanged += (_, e) => Console.WriteLine($"changed: {e.PropertyName}");

        person.Name = "Ada";    // changed: Name
        person.Name = "Grace";  // changed: Name
        // </PropertyChanged>
    }

    private static void ShowAttributeUsage()
    {
        // <AttributeUsage>
        // nameof works inside attribute arguments. The compiler resolves the
        // identifier even when the attribute targets a method or its parameters.
        Console.WriteLine(NormalizeOrNull("  hi  ") ?? "<null>");   // hi
        Console.WriteLine(NormalizeOrNull(null) ?? "<null>");        // <null>

        [return: NotNullIfNotNull(nameof(input))]
        static string? NormalizeOrNull(string? input) => input?.Trim();
        // </AttributeUsage>
    }

    private static void ShowUnboundGenerics()
    {
        // <UnboundGenerics>
        // Beginning in C# 14, nameof accepts an unbound generic type.
        // The result is the simple type name without any type-argument list.
        Console.WriteLine(nameof(List<>));                  // List
        Console.WriteLine(nameof(Dictionary<,>));           // Dictionary
        // </UnboundGenerics>
    }

    private static void ShowQualifiedName()
    {
        // <QualifiedName>
        // For a qualified expression, nameof returns only the final identifier.
        Console.WriteLine(nameof(System.Collections.Generic.List<int>)); // List
        Console.WriteLine(nameof(Customer.Name));                        // Name
        // </QualifiedName>
    }
}

// <CustomerType>
public sealed record Customer(string Name);
// </CustomerType>

// <PersonType>
public sealed class Person : INotifyPropertyChanged
{
    private string _name = "";

    public string Name
    {
        get => _name;
        set
        {
            if (_name == value) return;
            _name = value;
            // nameof keeps the property name and the change notification in sync.
            // Renaming the property automatically updates this argument.
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// </PersonType>

nameof in attribute arguments

nameof is valid inside attribute arguments. The compiler resolves identifiers in the surrounding scope, including the parameters of the method the attribute targets. This is the idiomatic way to refer to a parameter from an attribute such as System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute:

sample · csharpdocs/csharp/fundamentals/strings/snippets/nameof/Program.cs

csharp
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace NameofSample;

public static class Program
{
    public static void Main()
    {
        ShowBasic();
        ShowGuardClause();
        ShowThrowIfNull();
        ShowPropertyChanged();
        ShowAttributeUsage();
        ShowUnboundGenerics();
        ShowQualifiedName();
    }

    private static void ShowBasic()
    {
        // <Basic>
        // nameof produces the textual identifier of a symbol at compile time.
        Console.WriteLine(nameof(Customer));        // Customer
        Console.WriteLine(nameof(Customer.Name));   // Name

        var customer = new Customer("Ada");
        Console.WriteLine(nameof(customer));        // customer
        Console.WriteLine(nameof(customer.Name));   // Name
        // </Basic>
    }

    private static void ShowGuardClause()
    {
        // <GuardClause>
        try
        {
            Greet("");
        }
        catch (ArgumentException ex)
        {
            // The exception's ParamName is the literal "name", produced by nameof at compile time.
            Console.WriteLine($"{ex.ParamName}: {ex.Message}");
        }

        static void Greet(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Name must be non-empty.", nameof(name));
            }
            Console.WriteLine($"Hello, {name}!");
        }
        // </GuardClause>
    }

    private static void ShowThrowIfNull()
    {
        // <ThrowIfNull>
        // ArgumentNullException.ThrowIfNull captures the argument's name automatically
        // through [CallerArgumentExpression], so a separate nameof isn't required for
        // the null check. Use nameof for cases the helpers don't cover.
        Customer? maybeCustomer = null;

        try
        {
            Save(maybeCustomer);
        }
        catch (ArgumentNullException ex)
        {
            Console.WriteLine(ex.ParamName);   // customer
        }

        static void Save(Customer? customer)
        {
            ArgumentNullException.ThrowIfNull(customer);
            // ...
        }
        // </ThrowIfNull>
    }

    private static void ShowPropertyChanged()
    {
        // <PropertyChanged>
        var person = new Person();
        person.PropertyChanged += (_, e) => Console.WriteLine($"changed: {e.PropertyName}");

        person.Name = "Ada";    // changed: Name
        person.Name = "Grace";  // changed: Name
        // </PropertyChanged>
    }

    private static void ShowAttributeUsage()
    {
        // <AttributeUsage>
        // nameof works inside attribute arguments. The compiler resolves the
        // identifier even when the attribute targets a method or its parameters.
        Console.WriteLine(NormalizeOrNull("  hi  ") ?? "<null>");   // hi
        Console.WriteLine(NormalizeOrNull(null) ?? "<null>");        // <null>

        [return: NotNullIfNotNull(nameof(input))]
        static string? NormalizeOrNull(string? input) => input?.Trim();
        // </AttributeUsage>
    }

    private static void ShowUnboundGenerics()
    {
        // <UnboundGenerics>
        // Beginning in C# 14, nameof accepts an unbound generic type.
        // The result is the simple type name without any type-argument list.
        Console.WriteLine(nameof(List<>));                  // List
        Console.WriteLine(nameof(Dictionary<,>));           // Dictionary
        // </UnboundGenerics>
    }

    private static void ShowQualifiedName()
    {
        // <QualifiedName>
        // For a qualified expression, nameof returns only the final identifier.
        Console.WriteLine(nameof(System.Collections.Generic.List<int>)); // List
        Console.WriteLine(nameof(Customer.Name));                        // Name
        // </QualifiedName>
    }
}

// <CustomerType>
public sealed record Customer(string Name);
// </CustomerType>

// <PersonType>
public sealed class Person : INotifyPropertyChanged
{
    private string _name = "";

    public string Name
    {
        get => _name;
        set
        {
            if (_name == value) return;
            _name = value;
            // nameof keeps the property name and the change notification in sync.
            // Renaming the property automatically updates this argument.
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// </PersonType>

If the parameter is renamed, the nameof argument is updated by the same refactoring — the attribute can't fall out of date.

Qualified names

For any qualified expression, nameof returns only the last identifier:

sample · csharpdocs/csharp/fundamentals/strings/snippets/nameof/Program.cs

csharp
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace NameofSample;

public static class Program
{
    public static void Main()
    {
        ShowBasic();
        ShowGuardClause();
        ShowThrowIfNull();
        ShowPropertyChanged();
        ShowAttributeUsage();
        ShowUnboundGenerics();
        ShowQualifiedName();
    }

    private static void ShowBasic()
    {
        // <Basic>
        // nameof produces the textual identifier of a symbol at compile time.
        Console.WriteLine(nameof(Customer));        // Customer
        Console.WriteLine(nameof(Customer.Name));   // Name

        var customer = new Customer("Ada");
        Console.WriteLine(nameof(customer));        // customer
        Console.WriteLine(nameof(customer.Name));   // Name
        // </Basic>
    }

    private static void ShowGuardClause()
    {
        // <GuardClause>
        try
        {
            Greet("");
        }
        catch (ArgumentException ex)
        {
            // The exception's ParamName is the literal "name", produced by nameof at compile time.
            Console.WriteLine($"{ex.ParamName}: {ex.Message}");
        }

        static void Greet(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Name must be non-empty.", nameof(name));
            }
            Console.WriteLine($"Hello, {name}!");
        }
        // </GuardClause>
    }

    private static void ShowThrowIfNull()
    {
        // <ThrowIfNull>
        // ArgumentNullException.ThrowIfNull captures the argument's name automatically
        // through [CallerArgumentExpression], so a separate nameof isn't required for
        // the null check. Use nameof for cases the helpers don't cover.
        Customer? maybeCustomer = null;

        try
        {
            Save(maybeCustomer);
        }
        catch (ArgumentNullException ex)
        {
            Console.WriteLine(ex.ParamName);   // customer
        }

        static void Save(Customer? customer)
        {
            ArgumentNullException.ThrowIfNull(customer);
            // ...
        }
        // </ThrowIfNull>
    }

    private static void ShowPropertyChanged()
    {
        // <PropertyChanged>
        var person = new Person();
        person.PropertyChanged += (_, e) => Console.WriteLine($"changed: {e.PropertyName}");

        person.Name = "Ada";    // changed: Name
        person.Name = "Grace";  // changed: Name
        // </PropertyChanged>
    }

    private static void ShowAttributeUsage()
    {
        // <AttributeUsage>
        // nameof works inside attribute arguments. The compiler resolves the
        // identifier even when the attribute targets a method or its parameters.
        Console.WriteLine(NormalizeOrNull("  hi  ") ?? "<null>");   // hi
        Console.WriteLine(NormalizeOrNull(null) ?? "<null>");        // <null>

        [return: NotNullIfNotNull(nameof(input))]
        static string? NormalizeOrNull(string? input) => input?.Trim();
        // </AttributeUsage>
    }

    private static void ShowUnboundGenerics()
    {
        // <UnboundGenerics>
        // Beginning in C# 14, nameof accepts an unbound generic type.
        // The result is the simple type name without any type-argument list.
        Console.WriteLine(nameof(List<>));                  // List
        Console.WriteLine(nameof(Dictionary<,>));           // Dictionary
        // </UnboundGenerics>
    }

    private static void ShowQualifiedName()
    {
        // <QualifiedName>
        // For a qualified expression, nameof returns only the final identifier.
        Console.WriteLine(nameof(System.Collections.Generic.List<int>)); // List
        Console.WriteLine(nameof(Customer.Name));                        // Name
        // </QualifiedName>
    }
}

// <CustomerType>
public sealed record Customer(string Name);
// </CustomerType>

// <PersonType>
public sealed class Person : INotifyPropertyChanged
{
    private string _name = "";

    public string Name
    {
        get => _name;
        set
        {
            if (_name == value) return;
            _name = value;
            // nameof keeps the property name and the change notification in sync.
            // Renaming the property automatically updates this argument.
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// </PersonType>

If you need the fully qualified name, use System.Type.FullName on a Type instance. nameof is for identifiers, not paths.

Prefer nameof to identifier strings

Anywhere you refer to a method, property, parameter, type, or namespace by name in code, use nameof instead of a string literal. Compared to a hardcoded string:

  • The compiler verifies that the symbol exists. A typo becomes a build error, not a silent runtime bug.
  • Rename refactorings update the result automatically. Hardcoded strings drift out of sync.
  • The result is a compile-time constant, so there's no runtime cost.

This recommendation applies to logging messages, exception arguments, attribute arguments, property-change notifications, and serialization key constants tied to a member name.

See also