Section 2. Fundamentals. 2.2 C# Data types
Data types specify the type of data that a valid C# variable can hold. C# is a strongly typed programming language because in C#, each type of data (such as integer, character, float, and so on) is predefined as part of the programming language and all constants or variables defined for a given program must be described with one of the data types.


Value data types
In C#, the Value Data Types will directly store the variable value in memory and it will also accept both signed and unsigned literals. The derived class for these data types are System.ValueType.
1.Built-in value types
C# provides the following built-in value types, also known as simple types:
- Integral numeric types. The integral numeric types represent integer numbers. All integral numeric types are value types. They are also simple types and can be initialized with literals. All integral numeric types support arithmetic, bitwise logical, comparison, and equality operators. C# supports the following predefined integral types: sbyte, byte, short, ushort, int, uint, long, ulong, nint, nuint. Can be signed and unsigned;
- Floating-point numeric types. The floating-point numeric types represent real numbers. All floating-point numeric types are value types. They are also simple types and can be initialized with literals. All floating-point numeric types support arithmetic, comparison, and equality operators. C# supports the following predefined floating-point types: float, double;
- bool. The bool type keyword is an alias for the .NET System.Boolean structure type that represents a Boolean value, which can be either true or false. To perform logical operations with values of the bool type, use Boolean logical operators. The bool type is the result type of comparison and equality operators. A bool expression can be a controlling conditional expression in the if, do, while, and for statements and in the conditional operator?:. The default value of the bool type is false;
- char. The char type keyword is an alias for the .NET System.Char structure type that represents a Unicode UTF-16 character. The default value of the char type is \0, that is, U+0000. The char type supports comparison, equality, increment, and decrement operators. Moreover, for char operands, arithmetic and bitwise logical operators perform an operation on the corresponding character codes and produce the result of the int type. The string type represents text as a sequence of char values.
Please note that:
- Starting in C# 9.0, you can use the nint and nuint keywords to define native-sized integers. These are 32-bit integers when running in a 32-bit process, or 64-bit integers when running in a 64-bit process. They can be used for interop scenarios, low-level libraries, and to optimize performance in scenarios where integer math is used extensively.
- All simple types are structure types and differ from other structure types in that they permit certain additional operations:
- You can use literals to provide a value of a simple type. For example, ‘A’ is a literal of the type char and 2001 is a literal of the type int.
- You can declare constants of the simple types with the const keyword. It’s not possible to have constants of other structure types.
- Constant expressions, whose operands are all constants of the simple types, are evaluated at compile time.
- Use float or double? The precision of a floating point value indicates how many digits the value can have after the decimal point. The precision of float is only 6 or 9 decimal digits, while double variables have a precision of about 15 or 17 digits. Therefore it is safer to use double for most calculations.
2.Enumeration types
An enumeration type (or enum type) is a value type defined by a set of named constants of the underlying integral numeric type. To define an enumeration type, use the enum keyword and specify the names of enum members:
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
3.Structure types
A structure type (or struct type) is a value type that can encapsulate data and related functionality. You use the struct keyword to define a structure type:
public struct Coords
{
public Coords(double x, double y)
{
X = x;
Y = y;
}
public double X { get; }
public double Y { get; }
public override string ToString() => $"({X}, {Y})";
}
Structure types have value semantics. That is, a variable of a structure type contains an instance of the type. By default, variable values are copied on assignment, passing an argument to a method, and returning a method result. In the case of a structure-type variable, an instance of the type is copied. For more information, see Value types.
Typically, you use structure types to design small data-centric types that provide little or no behavior. For example, .NET uses structure types to represent a number (integer and real), a Boolean value, a Unicode character, a time instance. If you’re focused on the behavior of a type, consider defining a class. Class types have reference semantics. That is, a variable of a class type contains a reference to an instance of the type, not the instance itself.
Because structure types have value semantics, we recommend you to define immutable structure types.
4.Nullable value types
A nullable value type T?
represents all values of its underlying value type T
and an additional null value. For example, you can assign any of the following three values to a bool?
variable: true
, false
, or null
. An underlying value type T
cannot be a nullable value type itself. C# 8.0 introduces the nullable reference types feature. For more information, see Nullable reference types. The nullable value types are available beginning with C# 2.
Any nullable value type is an instance of the generic System.Nullable<T> structure. You can refer to a nullable value type with an underlying type T
in any of the following interchangeable forms: Nullable<T>
or T?
.
You typically use a nullable value type when you need to represent the undefined value of an underlying value type. For example, a Boolean, or bool
, variable can only be either true
or false
. However, in some applications a variable value can be undefined or missing. For example, a database field may contain true
or false
, or it may contain no value at all, that is, NULL
. You can use the bool?
type in that scenario.
As a value type is implicitly convertible to the corresponding nullable value type, you can assign a value to a variable of a nullable value type as you would do that for its underlying value type. You can also assign the null
value. For example:
double? pi = 3.14;
char? letter = 'a';
int m2 = 10;
int? m = m2;
bool? flag = null;
// An array of a nullable value type:
int?[] arr = new int?[10];
The default value of a nullable value type represents null
, that is, it’s an instance whose Nullable<T>.HasValue property returns false
.
5.Tuple types
Beginning with C# 7.0, C# supports value tuples. A value tuple is a value type, but not a simple type. The tuples feature provides concise syntax to group multiple data elements in a lightweight data structure. The following example shows how you can declare a tuple variable, initialize it, and access its data members:
(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.
(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.
Beginning with C# 7.3, tuple types support equality operators ==
and !=
. For more information, see the Tuple equality section. Tuple types are value types; tuple elements are public fields. That makes tuples mutable value types.
The tuples feature requires the System.ValueTuple type and related generic types (for example, System.ValueTuple<T1,T2>), which are available in .NET Core and .NET Framework 4.7 and later. To use tuples in a project that targets .NET Framework 4.6.2 or earlier, add the NuGet package System.ValueTuple
to the project.
You can define tuples with an arbitrary large number of elements:
var t =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
Console.WriteLine(t.Item26); // output: 26
Pointer data types
In an unsafe context, a type may be a pointer type, in addition to a value type, or a reference type. Pointer type variables store the memory address of another type. Pointers in C# have the same capabilities as the pointers in C or C++. A pointer type declaration takes one of the following forms:
type* identifier;
void* identifier; //allowed but not recommended
The type specified before the *
in a pointer type is called the referent type. Only an unmanaged type can be a referent type.
Pointer types don’t inherit from object and no conversions exist between pointer types and object
. Also, boxing and unboxing don’t support pointers. However, you can convert between different pointer types and between pointer types and integral types.
We will review pointer types in the article Unsafe Codes.
Reference Data Types
The Reference Data Types will contain a memory address of variable value because the reference types won’t store the variable value directly in memory.
1.Built-in reference types
C# has a number of built-in reference types. They have keywords or operators that are synonyms for a type in the .NET library.
- object. The
object
type is an alias for System.Object in .NET. In the unified type system of C#, all types, predefined and user-defined, reference types and value types, inherit directly or indirectly from System.Object. You can assign values of any type to variables of typeobject
. Anyobject
variable can be assigned to its default value using the literalnull
. When a variable of a value type is converted to object, it is said to be boxed. When a variable of typeobject
is converted to a value type, it is said to be unboxed. For more information, see Boxing and Unboxing. - string. The
string
type represents a sequence of zero or more Unicode characters.string
is an alias for System.String in .NET. Althoughstring
is a reference type, the equality operators==
and!=
are defined to compare the values ofstring
objects, not references. This makes testing for string equality more intuitive. - dynamic. The
dynamic
type indicates that use of the variable and references to its members bypass compile-time type checking. Instead, these operations are resolved at run time. Thedynamic
type simplifies access to COM APIs such as the Office Automation APIs, to dynamic APIs such as IronPython libraries, and to the HTML Document Object Model (DOM). Typedynamic
behaves like typeobject
in most circumstances. In particular, any non-null expression can be converted to thedynamic
type. Thedynamic
type differs fromobject
in that operations that contain expressions of typedynamic
are not resolved or type checked by the compiler. The compiler packages together information about the operation, and that information is later used to evaluate the operation at run time. As part of the process, variables of typedynamic
are compiled into variables of typeobject
. Therefore, typedynamic
exists only at compile time, not at run time. The following example contrasts a variable of typedynamic
to a variable of typeobject
. To verify the type of each variable at compile time, place the mouse pointer overdyn
orobj
in theWriteLine
statements. Copy the following code into an editor where IntelliSense is available. IntelliSense shows dynamic fordyn
and object forobj
.
class Program
{
static void Main(string[] args)
{
dynamic dyn = 1;
object obj = 1;
// Rest the mouse pointer over dyn and obj to see their
// types at compile time.
System.Console.WriteLine(dyn.GetType());
System.Console.WriteLine(obj.GetType());
}
}
The WriteLine statements display the run-time types of dyn
and obj
. At that point, both have the same type, integer. The following output is produced:
System.Int32
System.Int32
Please note that:
- C# 4.0 (.NET 4.5) introduced a new type called
dynamic
that avoids compile-time type checking. Adynamic
type escapes type checking at compile-time; instead, it resolves type at run time. A dynamic type variables are defined using thedynamic
keyword.
2.User defined data types
The following keywords are used to declare reference types:
- class. Classes are declared using the keyword
class
, as shown in the following example:
class MyClass
{
// Methods, properties, fields, events, delegates
// and nested classes go here.
}
- interface. An interface defines a contract. Any class or struct that implements that contract must provide an implementation of the members defined in the interface. Beginning with C# 8.0, an interface may define a default implementation for members. It may also define static members in order to provide a single implementation for common functionality. In the following example, class ImplementationClass must implement a method named SampleMethod that has no parameters and returns void. For more information and examples, see Interfaces.
interface ISampleInterface
{
void SampleMethod();
}
class ImplementationClass : ISampleInterface
{
// Explicit interface member implementation:
void ISampleInterface.SampleMethod()
{
// Method implementation.
}
static void Main()
{
// Declare an interface instance.
ISampleInterface obj = new ImplementationClass();
// Call the member.
obj.SampleMethod();
}
}
- delegate.The declaration of a delegate type is similar to a method signature. It has a return value and any number of parameters of any type:
public delegate void MessageDelegate(string message);
public delegate int AnotherDelegate(MyType m, long num);
In .NET, System.Action
and System.Func
types provide generic definitions for many common delegates. You likely don’t need to define new custom delegate types. Instead, you can create instantiations of the provided generic types.
A delegate
is a reference type that can be used to encapsulate a named or an anonymous method. Delegates are similar to function pointers in C++; however, delegates are type-safe and secure. For applications of delegates, see Delegates and Generic Delegates. Delegates are the basis for Events. A delegate can be instantiated by associating it either with a named or anonymous method.
- record. Beginning with C# 9, you use the record keyword to define a reference type that provides built-in functionality for encapsulating data. You can create record types with immutable properties by using positional parameters or standard property syntax:
public record Person(string FirstName, string LastName);
While records can be mutable, they’re primarily intended for supporting immutable data models.
Wrapping out
We have learnt about the data types used in C# programming language.
Thank you for reading.
Check for more tutorials at acoptex.lt.
Check for Arduino and Raspberry Pi projects on our website acoptex.com.
Section 1. Introduction. 1.1 C# programming language
Section 1. Introduction. 1.2 Introduction to .NET Framework
Section 1. Introduction. 1.3 C# versions history
Section 1. Introduction. 1.4 C# vs Java
Section 1. Introduction. 1.5 C# get started
Section 1. Introduction. 1.6 Your first program – Hello world
Section 1. Introduction. 1.7 C# identifiers and keywords
Section 2. Fundamentals. 2.1 C# Comments