Data Types ========================= A data **type** specifies the allowed size and kind of variable values. Data types are used when assigning values to variables. In programming, a variable is a name that refers to a value of a specific type at a memory location. A number ``10``, for example, is stored in the memory at certain location as ``1010``. As values can get extremely large or small or complex, or different in its composition, there is a need to decide how much memory to reserve for different kinds of values so that computer memories can be effectively used, hence the various data types. There are two catagories of data types in C#: **reference types** and **value types**. Variables of value types contain the data directly (memory address allocated and value assigned) while variables of reference types store reference to the data known as objects. Value types are predefined types and are available in C# to be used as keywords. These keywords are aliases of types defined in the .NET Class Library. For example, the C# keyword int is an alias to a value type defined in the .NET Class Library: System.Int32. [#f1]_ Common Data Types in C# ----------------------------- C# is a "strongly typed" language, meaning that every variable and constant has a type. In addition to variables, types are also required for expressions that evaluate to a value, method declarations with names, input parameters, and return values. Examples of common value types (declared and initiated with value assignment) are as follows. .. code-block:: console int myNum = 5; // Integer (whole number) long myNum = 15000000000L; // Integer double myDoubleNum = 5.99D; // Floating point number char myLetter = 'D'; // Character bool myBool = true; // Boolean string myText = "Hello"; // String From the list above you may notice that C# requires number **suffixes** for numeric types. The purpose of using suffixes is to help the compiler unambiguously identify the data type of the value/literal. The basic rules for integral literal number suffixes are: - If an integral literal has no suffix, its type is the first of the following types in which its value can be represented: int, uint, long, ulong. - l or L for long - U or u for unsigned integer - UL or ul for unsigned long For floating-point numeric types: - The number literal without suffix or with the d or D suffix is of type double - f or F suffix is of type float - m or M suffix is of type decimal A purpose of defining data types is for memory allocation. Examples of some defined types and their memory size can be seen in the table below. ============ ===================== =============== ================================================================== Type Size Precision Description ============ ===================== =============== ================================================================== ``byte`` 1 byte/8 bits Integral range 0 to 255 (no negative values) ``short`` 16 bits Integral range -32,768 to 32,767 ``int`` 4 bytes/32 bits Integral range -2,147,483,648 to 2,147,483,647 ``long`` 8 bytes/64 bits Integral range from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ``float`` 4 bytes/32 bits ~6-9 digits Fractional numbers. ``double`` 8 bytes/64 bits ~15-17 digits Fractional numbers ±1.5 x 10−45 to ±3.4 x 1038. ``decimal`` 16 bytes/128 bits 28-29 digits Fractional numbers ±1.0 x 10-28 to ±7.9228 x 1028 ``bool`` 1 bit True or false values ``char`` 2 bytes A single character/letter corresponding to Unicode character set, surrounded by single quotes ``string`` 2 bytes per character A sequence of characters, surrounded by double quotes ============ ===================== =============== ================================================================== Choosing types is a design decision to meet the needs of different use scenarios. For example, for financial unit, the *decimal* type may be a better choice because it is designed for the purpose of holding a larger range of digits with higher precision. One attribute of the integral value data types is that they can be either signed or unsigned. A signed type uses its bytes to represent an equal number of positive and negative numbers; whereas an unsigned type (such as ``ushort``, ``uint``, and ``ulong``) uses its bytes to represent only positive numbers. The difference between singed and unsigned numbers can be seen from the example below: .. code-block:: C# :linenos: Console.WriteLine("Signed integral types:"); Console.WriteLine($"short : {short.MinValue} to {short.MaxValue}"); Console.WriteLine($"int : {int.MinValue} to {int.MaxValue}"); Console.WriteLine($"long : {long.MinValue} to {long.MaxValue}"); Console.WriteLine(""); Console.WriteLine("Unsigned integral types:"); Console.WriteLine($"byte : {byte.MinValue} to {byte.MaxValue}"); Console.WriteLine($"ushort : {ushort.MinValue} to {ushort.MaxValue}"); Console.WriteLine($"uint : {uint.MinValue} to {uint.MaxValue}"); Console.WriteLine($"ulong : {ulong.MinValue} to {ulong.MaxValue}"); You should see the output as below: .. code-block:: C# Signed integral types: sbyte : -128 to 127 short : -32768 to 32767 int : -2147483648 to 2147483647 long : -9223372036854775808 to 9223372036854775807 Unsigned integral types: byte : 0 to 255 ushort : 0 to 65535 uint : 0 to 4294967295 ulong : 0 to 18446744073709551615 C# Built-in Types System -------------------------- C# has a type system with types defined that can be briefly described as follows. Reference types: There are 4 reference types: class type, interface type, array type, and delegate type. Under class type, types such as string and array are defined. For **value types**, C# defines a type system as follows. simple_type : numeric_type | 'bool' ; numeric_type : integral_type | floating_point_type | 'decimal' ; integral_type : 'sbyte' | 'byte' | 'short' | 'ushort' | 'int' | 'uint' | 'long' | 'ulong' | 'char' ; floating_point_type : 'float' | 'double' ; Type Conversion ---------------- C# variables have specific types but from time to time we may need our data to switch between the types. For example, when your program takes a user input for age, the input is of string type by default while you are looking for numeric type. You therefore need to cast string type to a numeric type. This switch may be *implicit* or *explicit*: Implicit Casting (automatically) - converting a smaller type to a larger type size char -> int -> long -> float -> double Explicit Casting (manually) - converting a larger type to a smaller size type double -> float -> long -> int -> char For instance, the conversion from type int to type long is implicit, so type int can implicitly be treated as type long. On the other hand, to convert from type long to type int, an explicit cast is required. Observe the example below to see that we put the desired result type name in **parentheses** as a *cast*. Also, we can use the ``GetType()`` method to get the type of an variable. You can test the examples below using ``csharprepl``. .. code-block:: c# > int a = 123; // variable a is assigned a value 123 > long b = a; // implicit conversion from int to long by reassignment > int c = (int) b; // explicit conversion from long to int > a.GetType() // use the GetType() function to get the type of the variable int > b.GetType() long > c.GetType() int When the types are not cast properly, C# will give error messages. For example: .. code-block:: none > double d = 2.0; > int i = d; ┌─────────────────────────────────────────CompilationErrorException─────────────────────────────────────────┐ │ (1,9): error CS0266: Cannot implicitly convert type 'double' to 'int'. An explicit conversion exists (are │ │ you missing a cast?) │ └───────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Note that if you choose to agree with the message and perform a type casting, you lose the precision of ``double`` over an ``int``. .. code-block:: none > double d = 2.5; // create a double type variable d > d 2.5 > int i; // declare an int without value assignment > i // get the (default) value of an int 0 > i = (int)d; // explicitly telling the compiler you intend the conversion > i // get the value of i; the value .5 is lost 2 > .. index:: Round function Rounding is similar to casting a floating type to possible as it gives us an ``int`` type. The function ``Math.Round`` will round to a mathematical integer, but leaves the type unchanged. So we need to perform a type casting after rounding: .. code-block:: none > d 2.7 > d.GetType() double > d = Math.Round(d); // rounding and re-assignment > d 3 > d.GetType() // the type remains double > i = (int)Math.Round(d); // casting to int > i 3 > i.GetType() // type correct int Casting from int to double is usually not necessary but cause of implicit conversion. A use case for this would be when doing divisions, where ``double`` would work better than ``int``. As an example, using csharprepl, we see that: .. code-block:: none > int denominator = 3; > int numerator = 14; > numerator / denominator // an integer division 4 > (double) numerator / denominator // intended operation; casting required 4.666666666666667 > .. 6 Built-in (Simple) C# Types .. ------------------------------ .. char .. ~~~~~~ .. The type for an individual character is ``char``. A ``char`` literal value is .. a *single* character enclosed in *single* quotes, like ``'a'`` or ``'$'``. .. Note that when a character is surrounded by double quotation marks, it becomes .. a string literal, such as ``"A"``. .. Also, the char type keyword is an alias for the .NET System.Char structure type that .. represents a Unicode UTF-16 character. Internally, a ``char`` is an integer, stored in 16 bits, .. with the correspondence between numeric codes and characters given by the .. *Unicode* standard. For example: .. .. code-block:: console .. var chars = new[] { // an implicitly typed array .. 'j', .. '\u006A', .. '\x006A', .. (char)106, .. }; .. Console.WriteLine(string.Join(" ", chars)); // output: j j j j .. As seen in the type system, the type char is one of the integral types used to represent .. characters. We can therefore cast char like below as an example:: .. > (int)'A'; .. 65 .. > (int)'+'; .. 43 .. An we can even perform arithmetical operations on chars like:: .. > Console.WriteLine('A' + '+'); .. 108 .. Boolean/bool .. ~~~~~~~~~~~~~~~~~~~~~~ .. The Boolean data type can only have one of two values and is used in conditional (if) .. statements, which allow us to build logic in our programs:: .. - YES / NO .. - ON / OFF .. - TRUE / FALSE .. The type *bool* is an alias for *System.Boolean* with literals of ``true`` and ``false``. .. A Boolean expression, on the hand, would return a boolean value of ``True`` or ``False`` .. as a result of comparing values/variables. For example:: .. int x = 10; .. int y = 9; .. Console.WriteLine(x > y); // returns True, because 10 is higher than 9 .. rubric:: Footnotes .. [#f1] For a list of all specified value types, see, for example: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/types#83-value-types tests