Declaration statements

A declaration statement declares a new local variable, local constant, or ref local variable. To declare a local variable, specify its type and provide its name. You can declare multiple variables of the same type in one statement, as the following example shows:

string greeting;
int a, b, c;
List<double> xs;

In a declaration statement, you can also initialize a variable with its initial value:

string greeting = "Hello";
int a = 3, b = 2, c = a + b;
List<double> xs = new();

The preceding examples explicitly specify the type of a variable. You can also let the compiler infer the type of a variable from its initialization expression. To do that, use the var keyword instead of a type's name. For more information, see the Implicitly-typed local variables section.

To declare a local constant, use the const keyword, as the following example shows:

const string Greeting = "Hello";
const double MinLimit = -10.0, MaxLimit = -MinLimit;

When you declare a local constant, you must also initialize it.

For information about ref local variables, see the Ref locals section.

Implicitly-typed local variables

When you declare a local variable, you can let the compiler infer the type of the variable from the initialization expression. To do that use the var keyword instead of the name of a type:

var greeting = "Hello";
Console.WriteLine(greeting.GetType());  // output: System.String

var a = 32;
Console.WriteLine(a.GetType());  // output: System.Int32

var xs = new List<double>();
Console.WriteLine(xs.GetType());  // output: System.Collections.Generic.List`1[System.Double]

As the preceding example shows, implicitly-typed local variables are strongly typed.

Note

When you use var in the enabled nullable aware context and the type of an initialization expression is a reference type, the compiler always infers a nullable reference type even if the type of an initialization expression isn't nullable.

A common use of var is with a constructor invocation expression. The use of var allows you to not repeat a type name in a variable declaration and object instantiation, as the following example shows:

var xs = new List<int>();

Beginning with C# 9.0, you can use a target-typed new expression as an alternative:

List<int> xs = new();
List<int>? ys = new();

When you work with anonymous types, you must use implicitly-typed local variables. The following example shows a query expression that uses an anonymous type to hold a customer's name and phone number:

var fromPhoenix = from cust in customers
                  where cust.City == "Phoenix"
                  select new { cust.Name, cust.Phone };

foreach (var customer in fromPhoenix)
{
    Console.WriteLine($"Name={customer.Name}, Phone={customer.Phone}");
}

In the preceding example, you can't explicitly specify the type of the fromPhoenix variable. The type is IEnumerable<T> but in this case T is an anonymous type and you cannot provide its name. That's why you need to use var. For the same reason, you must use var when you declare the customer iteration variable in the foreach statement.

For more information about implicitly-typed local variables, see Implicitly-typed local variables.

In pattern matching, the var keyword is used in a var pattern.

Ref locals

You add the ref keyword before the type of a variable to declare a ref local. A ref local is a variable that refers to other storage. Assume the GetContactInformation method is declared as a ref return:

public ref Person GetContactInformation(string fname, string lname)

Let's contrast these two assignments:

Person p = contacts.GetContactInformation("Brandie", "Best");
ref Person p2 = ref contacts.GetContactInformation("Brandie", "Best");

The variable p holds a copy of the return value from GetContactInformation. It's a separate storage location from the ref return from GetContactInformation. If you change any property of p, you are changing a copy of the Person.

The variable p2 refers to the storage location for the ref return from GetContactInformation. It's the same storage as the ref return from GetContactInformation. If you change any property of p2, you are changing that single instance of a Person.

You can access a value by reference in the same way. In some cases, accessing a value by reference increases performance by avoiding a potentially expensive copy operation. For example, the following statement shows how one can define a ref local value that is used to reference a value.

ref VeryLargeStruct reflocal = ref veryLargeStruct;

The ref keyword is used both before the local variable declaration and before the value in the second example. Failure to include both ref keywords in the variable declaration and assignment in both examples results in compiler error CS8172, "Can't initialize a by-reference variable with a value."

ref VeryLargeStruct reflocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.

Ref local variables must still be initialized when they're declared.

The following example defines a NumberStore class that stores an array of integer values. The FindNumber method returns by reference the first number that is greater than or equal to the number passed as an argument. If no number is greater than or equal to the argument, the method returns the number in index 0.

using System;

class NumberStore
{
    int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };

    public ref int FindNumber(int target)
    {
        for (int ctr = 0; ctr < numbers.Length; ctr++)
        {
            if (numbers[ctr] >= target)
                return ref numbers[ctr];
        }
        return ref numbers[0];
    }

    public override string ToString() => string.Join(" ", numbers);
}

The following example calls the NumberStore.FindNumber method to retrieve the first value that is greater than or equal to 16. The caller then doubles the value returned by the method. The output from the example shows the change reflected in the value of the array elements of the NumberStore instance.

var store = new NumberStore();
Console.WriteLine($"Original sequence: {store.ToString()}");
int number = 16;
ref var value = ref store.FindNumber(number);
value *= 2;
Console.WriteLine($"New sequence:      {store.ToString()}");
// The example displays the following output:
//       Original sequence: 1 3 7 15 31 63 127 255 511 1023
//       New sequence:      1 3 7 15 62 63 127 255 511 1023

Without support for reference return values, such an operation is performed by returning the index of the array element along with its value. The caller can then use this index to modify the value in a separate method call. However, the caller can also modify the index to access and possibly modify other array values.

The following example shows how the FindNumber method could be rewritten to use ref local reassignment:

using System;

class NumberStore
{
    int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };

    public ref int FindNumber(int target)
    {
        ref int returnVal = ref numbers[0];
        var ctr = numbers.Length - 1;
        while ((ctr >= 0) && (numbers[ctr] >= target))
        {
            returnVal = ref numbers[ctr];
            ctr--;
        }
        return ref returnVal;
    }

    public override string ToString() => string.Join(" ", numbers);
}

This second version is more efficient with longer sequences in scenarios where the number sought is closer to the end of the array, as the array is iterated from end towards the beginning, causing fewer items to be examined.

The compiler enforces scope rules on ref variables: ref locals, ref parameters, and ref fields in ref struct types. The rules ensure that a reference doesn't outlive the object to which it refers. See the section on scoping rules in the article on method parameters.

ref and readonly

The readonly modifier can be applied to ref local variables and ref fields. The readonly modifier affects the expression to its right. See the following example declarations:

ref readonly int aConstant; // aConstant can't be value-reassigned.
readonly ref int Storage; // Storage can't be ref-reassigned.
readonly ref readonly int CantChange; // CantChange can't be value-reassigned or ref-reassigned.
  • value reassignment means the value of the variable is reassigned.
  • ref assignment means the variable now refers to a different object.

The readonly ref and readonly ref readonly declarations are valid only on ref fields in a ref struct.

scoped ref

The contextual keyword scoped restricts the lifetime of a value. The scoped modifier restricts the ref-safe-to-escape or safe-to-escape lifetime, respectively, to the current method. Effectively, adding the scoped modifier asserts that your code won't extend the lifetime of the variable.

You can apply scoped to a parameter or local variable. The scoped modifier may be applied to parameters and locals when the type is a ref struct. Otherwise, the scoped modifier may be applied only to local variables that are ref types. That includes local variables declared with the ref modifier and parameters declared with the in, ref or out modifiers.

The scoped modifier is implicitly added to this in methods declared in a struct, out parameters, and ref parameters when the type is a ref struct.

C# language specification

For more information, see the Declaration statements section of the C# language specification.

For more information about the scoped modifier, see the Low-level struct improvements proposal note.

See also