Data Types
From Chapter 1’s HelloWorld program, you got a feel for the C# language, its structure, basic syntax characteristics, and how to write the simplest of programs. This chapter continues to discuss the C# basics by investigating the fundamental C# types.
Until now, you have worked with only a few built-in data types, with little explanation. In C# thousands of types exist, and you can combine types to create new types. A few types in C#, however, are relatively simple and are considered the building blocks of all other types. These types are the predefined types. The C# language’s predefined types include 10 integer types, two binary floating-point types for scientific calculations and one decimal float for financial calculations, one Boolean type, and a character type. This chapter investigates these types and looks more closely at the string type.
All built-in types have essentially three name forms. Consider the string type, for example. There is the full name (i.e., System.String), the implied or simplified name (i.e., String), and the keyword (i.e., string).
The full name provides all the context and disambiguates the type from all other types. This is the name used in the underlying CIL code into which C# compiles. The full name is comprised of the namespace (everything in the fully qualified type’s name before the last period) followed by the type name (the final part of the name after the last period). If it wasn’t for some C# conveniences, all types within C# would be referenced by their full name. All the predefined types themselves are part of the Base Class Library (BCL)—the name given to the set of APIs that comprise the underlying framework. The full name of the types included in the BCL, therefore, is also the BCL name.
The conveniences are shortcuts that allow the compiler to resolve a type’s namespace implicitly rather than providing the namespace with each reference. Types with long namespaces such as System.Text.RegularExpressions.RegEx benefit a lot from the shortcut of avoiding the namespace qualifier (i.e., RegEx). See “Using Directives” in Chapter 5 for more information about the shortcuts and how the compiler can infer namespaces.
The keyword is a shortcut built into the C# language specifically for the predefined types. Types that are not predefined don’t have a keyword. Console from Chapter 1, for example, has only the full name (i.e., System.Console) and simpler non-qualified name (Console) with the namespace inferred. Similarly, any of the custom types that you define (such as Program) don’t have a keyword shortcut.
Developers have a choice of which of the three name forms to use when. Rather than switching back and forth, it is better to use one consistently. While there is seemingly little difference between using the unqualified name versus the keyword, C# developers generally use the C# keyword form (when available)—choosing, for example, string rather than String or System.String and int rather than Int32 or System.Int32. Using the fully qualified name from within C# code is rare and generally reserved to disambiguate types with the same unqualified name (such as System.Timers.Timer and System.Threading.Timer).
The choice for consistency often may be at odds with other guidelines. For example, given the guideline to use the C# keyword in place of the full name, there may be occasions when you find yourself maintaining a file (or library of files) with the opposite style. In these cases, it would be better to stay consistent with the previous style than to inject a new style and inconsistencies in the conventions. Even so, if the “style” was a bad coding practice that was likely to introduce bugs and obstruct successful maintenance, by all means correct the issue throughout.
The full name of the Console class that we have used for reading and writing content from the command line is System.Console. The System portion of the name is the namespace. Namespaces provide a hierarchical grouping of types. For example, the RegEx class (used for parsing text with regular expressions) is located in the System.Text.RegularExpressions namespace. Each period in the namespace represents a different level within the hierarchy.
For example, you can place a using directive at the top of a file, using System, for example, thereby eliminating the need to prefix a type with the namespace because the compiler can assume the namespace from the using directives.
C# 10 also includes a global using directive such that the using directive is required only once for the entire project, instead of repeatedly within each file that leverages types within the namespace. To identify a using directive as global, you can prefix the statement with global as in global using System.
In addition, C# 10 generates a default set of global using statements. A lookback at the enable value of the ImplicitUsings element from Listing 1.2 turns this on. Once enabled, it is no longer necessary to provide using directives or fully qualified names for any types in the generated set including System. Specifically, the C# compiler generates a .cs file that contains a set of global using statements.