Implicitly Typed Local Variables

The contextual keyword var is used for declaring an implicitly typed local variable.4 If the code initializes a variable at declaration time with an expression of unambiguous type, C# allows for the variable data type to be implied rather than stated, as shown in Listing 3.3.

Listing 3.3: Working with Strings
Console.Write("Enter text: ");
var text = Console.ReadLine();
 
// Return a new string in uppercase
var uppercase = text.ToUpper();
 
Console.WriteLine(uppercase);

This listing differs from the Working with Strings listing in Chapter 2 (Listing 2.24) in two ways. First, rather than using the explicit data type string for the declaration, Listing 3.3 uses var. The resultant CIL code is identical to using string explicitly. However, var indicates to the compiler that it should infer the data type from the value assigned within the declaration—in this case, the value returned by Console.ReadLine().

Second, the variables text and uppercase are initialized by their declarations. Not doing so would result in an error at compile time. As mentioned earlier, the compiler determines the data type of the initializing expression and declares the variable accordingly, just as it would if the programmer had specified the type explicitly.

Although using var rather than the explicit data type is allowed, consider avoiding such use when the data type is not obvious—for example, use string for the declaration of text and uppercase. Not only does this make the code more understandable, but it also allows the compiler to verify your intent, that the data type returned by the right-hand side expression is the type expected. When using a var-declared variable, the right-hand side data type should be obvious; if it isn’t, use of the var declaration should be avoided.

Guidelines
AVOID using implicitly typed local variables (var) unless the data type of the assigned value is obvious.
Language Contrast: C++/Visual Basic/JavaScript—void*, Variant, and var

An implicitly typed variable is not the equivalent of void* in C++, Variant in Visual Basic, or var in JavaScript. In each of these cases, the variable declaration is less restrictive because the variable may be assigned a value of any type, just as can be done in C# with a variable declaration of type object. In contrast, var is definitively typed by the compiler; once established at declaration, the type may not change, and type checks and member calls are verified at compile time.

Tuples

On occasion, you will find it useful to combine data elements together. Consider, for example, information about a country such as the poorest country in the world in 2022: Burundi, whose capital is Bujumbura, with a GDP per capita of $263.67. Given the constructs we have established so far, we could store each data element in individual variables, but the result would be no association of the data elements together. That is, $263.67 would have no association with Burundi, except perhaps by a common suffix or prefix in the variable names. Another option would be to combine all the data into a single string, albeit with the disadvantage that to work with each data element individually would require parsing it out.

C# 7.0 provides a third option, a tuple. Tuples allow you to combine the assignment to each variable in a single statement, as shown here for the country data:

(string country, string capital, double gdpPerCapita) =

     ("Burundi", "Bujumbura", 263.67);

Tuples have several additional syntax possibilities, as shown in Table 3.1.

Table 3.1: Sample Code for Tuple Declaration and Assignment

Description

Example Code

Ex. 1

Assign a tuple to individually declared variables.

(string country, string capital, double gdpPerCapita) =

     ("Burundi", "Bujumbura", 263.67);

Console.WriteLine(

     $@"The poorest country in the world in 2017 was {

         country}, {capital}: {gdpPerCapita}");

Ex. 2

Assign a tuple to individually declared variables that are pre-declared.

string country;

string capital;

double gdpPerCapita;

(country, capital, gdpPerCapita) =

     ("Burundi", "Bujumbura", 263.67);

Console.WriteLine(

     $@"The poorest country in the world in 2017 was {

         country}, {capital}: {gdpPerCapita}");

Ex. 3

Assign a tuple to individually declared and implicitly typed variables.

(var country, var capital, var gdpPerCapita) =

     ("Burundi", "Bujumbura", 263.67);

Console.WriteLine(

     $@"The poorest country in the world in 2017 was {

         country}, {capital}: {gdpPerCapita}");

Ex. 4

Assign a tuple to individually declared variables that are implicitly typed with a distributive syntax.

var (country, capital, gdpPerCapita) =

     ("Burundi", "Bujumbura", 263.67);

Console.WriteLine(

     $@"The poorest country in the world in 2017 was {

         country}, {capital}: {gdpPerCapita}");

Ex. 5

Declare a named item tuple and assign it tuple values, and then access the tuple items by name.

(string Name, string Capital, double GdpPerCapita) countryInfo =

     ("Burundi", "Bujumbura", 263.67);

Console.WriteLine(

     $@"The poorest country in the world in 2017 was {

         countryInfo.Name}, {countryInfo.Capital}: {

         countryInfo.GdpPerCapita}");

Ex. 6

Assign a named item tuple to a single implicitly typed variable that is implicitly typed, and then access the tuple items by name.

var countryInfo =

   (Name: "Burundi", Capital: "Bujumbura", GdpPerCapita: 263.67);

Console.WriteLine(

   $@"The poorest country in the world in 2017 was {

   countryInfo.Name}, {countryInfo.Capital}: {

   countryInfo.GdpPerCapita}");

Ex. 7

Assign an unnamed tuple to a single implicitly typed variable, and then access the tuple elements by their item-number property.

Var countryInfo =

     ("Burundi", "Bujumbura", 263.67);

Console.WriteLine(

   $@"The poorest country in the world in 2017 was {

   countryInfo.Item1}, {countryInfo.Item2}: {

   countryInfo.Item3}");

Ex. 8

Assign a named item tuple to a single implicitly typed variable, and then access the tuple items by their item-number property.

var countryInfo =

   (Name: "Burundi", Capital: "Bujumbura", GdpPerCapita: 263.67);

Console.WriteLine(

   $@"The poorest country in the world in 2017 was {

   countryInfo.Item1}, {countryInfo.Item2}: {

   countryInfo.Item3}");

Ex. 9

Discard portions of the tuple with underscores.

(string name, _, double gdpPerCapita) =

     ("Burundi", "Bujumbura", 263.67);

Ex. 10

Tuple element names can be inferred from variable and property names (starting in C# 7.1).

string country = "Burundi";

string capital = "Bujumbura";

double gdpPerCapita = 263.67;

var countryInfo =

     (country, capital, gdpPerCapita);

Console.WriteLine(

     $@"The poorest country in the world in 2017 was {

         countryInfo.country}, {countryInfo.capital}: {

         countryInfo.gdpPerCapita}");

In the first four examples, although the right-hand side represents a tuple, the left-hand side still represents individual variables that are assigned together using tuple syntax, a syntax involving two or more elements separated by commas and associated together with parentheses. (The term tuple syntax is used here because the underlying data type that the compiler generates on the left-hand side isn’t technically a tuple.) The result is that although we start with values combined as a tuple on the right, the assignment to the left deconstructs the tuple into its constituent parts. In Example 2, the left-hand side assignment is to pre-declared variables. However, in Examples 1, 3, and 4, the variables are declared within the tuple syntax. Given that we are only declaring variables, the naming and casing convention follows the guidelines we discussed in Chapter 1—“DO use camelCase for local variable names,” for example.

Note that although implicit typing (var) can be distributed across each variable declaration within the tuple syntax, as shown in Example 4, you cannot do the same with an explicit type (such as string). Since tuples allow each item to be a different data type, distributing the explicit type name across all elements wouldn’t necessarily work unless all the item data types were identical (and even then, the compiler doesn’t allow it).

In Example 5, we declare a tuple on the left-hand side and then assign the tuple on the right. Note that the tuple has named items—names that we can then reference to retrieve the item values from the tuple. This is what enables the countryInfo.Name, countryInfo.Capital, and countryInfo.GdpPerCapita syntax in the Console.WriteLine statement. The result of the tuple declaration on the left is a grouping of the variables into a single variable (countryInfo) from which you can then access the constituent parts. This is useful because, as discussed in Chapter 4, you can then pass this single variable around to other methods, and those methods will also be able to access the individual items within the tuple.

As already mentioned, variables defined using tuple syntax use camelCase. However, the convention for tuple item names is not well defined. Suggestions include using parameter naming conventions when the tuple behaves like a parameter—such as when returning multiple values that before tuple syntax would have used out parameters. The alternative is to use PascalCase, following the naming convention for members of a type (properties, functions, and public fields, as discussed in Chapters 5 and 6). We strongly favor the latter approach of PascalCase, consistent with the casing convention of all member identifiers in C# and .NET. Even so, since the convention isn’t broadly agreed upon, we use the word CONSIDER rather than DO in the guideline, “CONSIDER using PascalCasing for all tuple item names.”

Guidelines
DO use camelCasing for variable declarations using tuple syntax.
CONSIDER using PascalCasing for all tuple item names.

Example 6 provides the same functionality as Example 5, although it uses named tuple items on the right-hand side tuple value and an implicit type declaration on the left. The items’ names are persisted to the implicitly typed variable, however, so they are still available for the WriteLine statement. Of course, this opens up the possibility that you could name the items on the left-hand side with different names than what you use on the right. While the C# compiler allows this, it will issue a warning that the item names on the right will be ignored, as those on the left will take precedence.

If no item names are specified, the individual elements are still available from the assigned tuple variable. However, the names are Item1, Item2, ..., as shown in Example 7. In fact, the ItemX names are always available on the tuple even when custom names are provided (see Example 8). However, when using integrated development environment (IDE) tools such as one of the flavors of Visual Studio (one that supports C# 7.0 or higher), the ItemX property will not appear within the IntelliSense dropdown—a good thing, since presumably the provided name is preferable. As shown in Example 9, portions of a tuple assignment can be discarded using an underscore—referred to as a discard.

The ability to infer the tuple item names as shown in Example 10 wasn’t introduced until C# 7.1. As the example demonstrates, the item name within the tuple can be inferred from a variable name or even a property name.

Tuples are a lightweight solution for encapsulating data into a single object in the same way that a bag might capture miscellaneous items you pick up from the store. Unlike arrays (which we discuss next), tuples contain item data types that can vary without constraint,5 except that they are identified by the code and cannot be changed at runtime. Also, unlike with arrays, the number of items within the tuple is hardcoded at compile time. Lastly, you cannot add custom behavior to a tuple (extension methods notwithstanding). If you need behavior associated with the encapsulated data, then leveraging object-oriented programming and defining a class is the preferred approach—a concept we begin exploring in depth in Chapter 6.

AdVanced Topic
The System.ValueTuple<...> Type

The C# compiler generates code that relies on a set of generic value types (structs), such as System.ValueTuple<T1, T2, T3>, as the underlying implementation for the tuple syntax for all tuple instances on the right-hand side of the examples in Table 3.1. Similarly, the same set of System.ValueTuple<...> generic value types is used for the left-hand side data type starting with Example 5. As we would expect with a tuple type, the only methods included are those related to comparison and equality.

Given that the custom item names and their types are not included in the System.ValueTuple<...> definition, how is it possible that each custom item name is seemingly a member of the System.ValueTuple<...> type and accessible as a member of that type? What is surprising (particularly for those familiar with the anonymous type implementation) is that the compiler does not generate underlying CIL code for the members corresponding to the custom names. However, even without an underlying member with the custom name, there is (seemingly), from the C# perspective, such a member.

For all the named tuple examples in Table 3.1, it is clearly possible that the names could be known by the compiler for the remainder of the scope of the tuple, since said scope is bounded within the member in which it is declared. And, in fact, the compiler (and IDE) quite simply rely on this scope to allow accessing each item by name. In other words, the compiler looks at the item names within the tuple declaration and leverages them to allow code that uses those names within the scope. It is for this reason as well that the ItemX names are not shown in the IDE IntelliSense as available members on the tuple.

Determining the item names from those scoped within a member is reasonable for the compiler, but what happens when a tuple is exposed outside the member, such as a parameter or return from a method that is in a different assembly (for which there is possibly no source code available)? For all tuples that are part of the API (whether a public or private API), the compiler adds item names to the metadata of the member in the form of attributes. For example, the C# equivalent of what the compiler generates for

     public (string First, string Second) ParseNames(string fullName)

is shown in Listing 3.4.

Listing 3.4: The C# Equivalent of Compiler-Generated CIL Code for a ValueTuple Return
[return: System.Runtime.CompilerServices.TupleElementNames(
    new { "First""Second" })]
public System.ValueTuple<stringstring> ParseNames(
    string fullName)
{
    // ...
}

On a related note, C# 7.0 does not enable the use of custom item names when using the explicit System.ValueTuple<...> data type. Therefore, if you replace var in Example 8 of Table 3.1, you end up with warnings that each item name will be ignored.

Here are a few additional miscellaneous facts to keep in mind about System.ValueTuple<...>.

There are eight generic System.ValueTuple<...>s corresponding to the possibility of supporting tuples with up to seven items. For the eighth tuple, System.ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>, the last type parameter allows specifying an additional ValueTuple, thus enabling support for n items. If, for example, you specify a tuple with eight parameters, the compiler automatically generates a System.ValueTuple<T1, T2, T3, T4, T5, T6, T7, System.ValueTuple<TSub1>> as the underlying implementing type. (For completeness, the System.ValueTuple<T1> exists but will rarely be used, since the C# tuple syntax requires a minimum of two items.)
There is a non-generic System.ValueTuple that serves as a tuple factory with Create() methods corresponding to each ValueTuple arity. The ease of using a tuple literal var t1 = ("Inigo Montoya", 42) supersedes the Create() method at least for C# 7.0 (or later) programmers.
For all practical purposes, C# developers can essentially ignore System.ValueTuple and System.ValueTuple<T>.

There is another tuple type that was first included with the Microsoft .NET Framework 4.5: System.Tuple<...>. At the time it was included, it was expected to be the core tuple implementation going forward. However, once C# supported tuple syntax, it was realized that a value type was generally more performant. In turn, System.ValueTuple<...> was introduced—effectively replacing System.Tuple<...> in all cases except backward compatibility with existing APIs that depend on System.Tuple<...>.

AdVanced Topic
Anonymous Types

Support for var was added to the language in C# 3.0 to permit the use of anonymous types. Anonymous types are data types that are declared “on the fly” within a method rather than through explicit class definitions, as shown in Listing 3.5 with Output 3.1. (See Chapter 15 for more details on anonymous types.)

Listing 3.5: Implicit Local Variables with Anonymous Types
var patent1 =
    new { Title = "Bifocals",
        YearOfPublication = "1784" };
var patent2 =
    new { Title = "Phonograph",
        YearOfPublication = "1877" };
 
Console.WriteLine(
    $"{patent1.Title} ({patent1.YearOfPublication})");
Console.WriteLine(
    $"{patent2.Title} ({patent2.YearOfPublication})");
Output 3.1
Bifocals (1784)
Phonograph (1877)

Listing 3.5 demonstrates the anonymous type assignment to an implicitly typed (var) local variable. This type of operation provided critical functionality in tandem with C#’s support for joining (associating) data types or reducing the size of a particular type down to fewer data elements. However, C# 7.0 introduces syntax for tuples, which all but replaces the need for anonymous types.

________________________________________

4. Introduced in C# 3.0.
5. Technically, they can’t be pointers—a topic we introduce in Chapter 23.
{{ snackbarMessage }}
;