data types are provided in the System
namespace. All of them, however, have a C# alias. These aliases are keywords in the C# language
The integral types
The default value of all integral types is 0. All of these types define two constants called MinValue
and MaxValue
, which provide the minimum and maximum value of the type.
Integral literals, which are numbers that appear directly in code (such as 0, -42, and so on), can be specified as decimal, hexadecimal, or binary literals. Decimal literals do not require any suffix. Hexadecimal literals are prefixed with 0x
or 0X
, and binary literals are prefixed with 0b
or 0B
. An underscore (_
) can be used as a digit separator with all numeric literals.
int dec = 32;
int hex = 0x2A;
int bin = 0b_0010_1010;
An integral value without any suffix is inferred by the compiler as int
. To indicate a long integer, use l
or L
for a signed 64-bit integer and ul
or UL
for an unsigned 64-bit integer.
The floating-point types
The default value for floating-point types is 0. These types also define two constants called MinValue
and MaxValue
that provide the minimum and maximum value of the type. However, these types also provide constants that represent not-a-number (System.Double.NaN
) and infinity (System.Double.NegativeInfinity
and System.Double.PositiveInfinity
)
var a = 42.99;//double
float b = 19.50f;
System.Double c = -1.23;
The decimal type
The decimal
type can represent up to 28 decimal places or 128 bits. is important to note that the decimal
type minimizes errors during rounding but does not eliminate the need for rounding. For instance, the result of the operation 1m / 3 * 3
is not 1 but 0.9999999999999999999999999999
. On the other hand, Math.Round(1m / 3 * 3)
yields the value 1.
decimal a = 42.99m;
var b = 12.45m;
System.Decimal c = 100.75M;
The char type
16 bits
char a = 'A';
char b = '\x0065';
char c = '\u15FE';
The string type
A string is an array of characters. In C#, the type for representing a string is called string
and is an alias for the .NET System.String
. You can use any of these two types interchangeably. Internally, a string contains a read-only collection of char
objects. This makes strings immutable, which means that you cannot change a string but need to create a new one every time you want to modify the content of an existing string. Strings are not null-terminated (unlike other languages such as C++) and can contain any number of null characters ('\0'
).
string s1; // unitialized
string s2 = null; // initialized with null
string s3 = String.Empty; // empty string
string s4 = "hello world"; // initialized with text
var s5 = "hello world";
System.String s6 = "hello world";
char[] letters = { 'h', 'e', 'l', 'l', 'o'};
string s7 = new string(letters); // from an array of chars
char c = s4[0]; // OK
s4[0] = 'H'; // error
var s8 = s6.Remove(5); // hello
var s9 = s6.ToUpper(); // HELLO WORLD
formats
int i = 42;
string s1 = "This is item " + i.ToString();
string s2 = string.Format("This is item {0}", i);
string s3 = $"This is item {i}";
string s4 = @"\b\w+\b";
The object type
The object
type is the base type for all other types in C#, even though you do not specify this explicitly, as we will see in the following chapters. The object
keyword in C# is an alias for the .NET System.Object
type. You can use these two interchangeably.
GetType()
method, which is not virtual and which returns a System.Type
object with information about the type of the current instance.
Equals()
behavior is different for reference and value types. for reference types, this method performs reference equality; this means it checks whether the two variables point to the same object on the heap. For value types, it performs value equality; this means that the two variables are of the same type and that the public and private fields of the two objects are equal.
The default value of a variable of the object
type is null
Reference types and value types
value types All user-defined types declared as structures (with the struct
keyword) are value types. Although all types are implicitly derived from the object
, type value types do not support explicit inheritance
Value types are typically stored on the stack in memory
int a = 20;
DateTime dt = new DateTime(2019, 12, 25);
int a = 20;
int b = a; // b is 20
a = 42; // a is 42, b is 20
reference type does not contain the value directly but a reference to a memory location where the actual value is stored. The built-in data types object
and string
are reference types. Arrays, interfaces, delegates, and any user-defined type defined as a class are also called reference types.
Nullable types
Reference types have the default value null
, which indicates that a variable is not assigned to the instance of any object. Value types do not have such an option. However, there are cases when no value is a valid value for a value type too. To represent such cases, you can use a nullable type.
A nullable type is an instance of System.Nullable, a generic value type that can represent the values of an underlying T type, which can only be a value type, as well as an additional null value.
Nullable<int> a;
Nullable<int> b = null;
Nullable<int> c = 42;
int? a;
int? b = null;
int? c = 42;
You can use the HasValue
property to check whether a nullable type object has a value, and Value
to access the underlying value:
if (c.HasValue)
Console.WriteLine(c.Value);
string? s1 = null; // OK, nullable type
string s2 = null; // error, non-nullable type
- You assign values to a nullable type object the same way you would assign to the underlying type.
- You can use the
GetValueOrDefault()
method to get either the assigned value or the default value of the underlying type if no value is assigned. - Boxing is performed on the underlying type. If the nullable type object has not assigned any value, the result of boxing is a
null
object. - You can use the null-coalescing operator,
??
, to access the value of the object of a nullable type (for example,int d = c ?? -1;
).
Array
int[] arr1;
int[] arr2 = null;
int[] arr3 = new int[6];
int[] arr4 = new int[] { 1, 1, 2, 3, 5, 8 };
int[] arr5 = new int[6] { 1, 1, 2, 3, 5, 8 };
int[] arr6 = { 1, 1, 2, 3, 5, 8 };
foreach(int element in arr6)
Console.WriteLine(element);
int[,] arr1;
arr1 = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
int[,] arr2 = null;
int[,] arr3 = new int[2,3];
int[,] arr4 = new int[,] { { 1, 2, 3 }, { 4, 5, 6 } };
int[,] arr5 = new int[2,3] { { 1, 2, 3 }, { 4, 5, 6 } };
int[,] arr6 = { { 1, 2, 3 }, { 4, 5, 6 } };
int[][] arr1;
int[][] arr2 = null;
int[][] arr3 = new int[2][];
arr3[0] = new int[3];
arr3[1] = new int[] { 1, 1, 2, 3, 5, 8 };
int[][] arr4 = new int[][]
{
new int[] { 1, 2, 3 },
new int[] { 1, 1, 2, 3, 5, 8 }
};
int[][] arr5 =
{
new int[] { 1, 2, 3 },
new int[] { 1, 1, 2, 3, 5, 8 }
};
int[][,] arr6 = new int[][,]
{
new int[,] { { 1, 2}, { 3, 4 } },
new int[,] { {11, 12, 13}, { 14, 15, 16} }
};
Type Conversion
string s = "example";
object o = s; // implicit conversion
string r = (string)o; // explicit conversion
public readonly struct fancyint
{
private readonly int value;
public fancyint(int value)
{
this.value = value;
}
public static implicit operator int(fancyint v) => v.value;
public static explicit operator fancyint(int v) => new fancyint(v);
public override string ToString() => $"{value}";
}
fancyint a = new fancyint(42);
int i = a; // implicit conversion
fancyint b = (fancyint)i; // explicit conversion
DateTime dt1 = DateTime.Parse("2019.08.31");
DateTime.TryParse("2019.08.31", out DateTime dt2);
int i1 = int.Parse("42"); // successful, i1 = 42
int i2 = int.Parse("42.15"); // error, throws exception
int.TryParse("42.15", out int i3); // error, returns false,
// i3 = 0
Operators
The null-conditional operators
The null-conditional operator has two forms: ?.
(also known as the Elvis operator) to apply member access and ?[]
to apply element access for an array. These operators apply the operation to their operand if and only if that operand is not null
. Otherwise, the result of applying the operator is also null
if(a is null)
a = b;
//or
a??=b;