In this article
- Variables
- Data Types
- Syntax
- Predefined Types
- Custom Types
- Type Conversion
- Value Types Versus Reference Types
Variables
A variable denotes a storage location that can contain different values over time.
Data Types
- A type defines the blueprint for a value.
- Datatypes are sets (ranges) of values that have similar characteristics. For instance, byte type specifies the set of integers in the range of [0…255].
Characteristics
Data types are characterized by :
- Name – for example, int;
- Size (how much memory they use) – for example, 4 bytes;
- Default value – for example 0.
Members of a Type
A type contains :
- Data members. (fields)
- Function members. (methods)
Syntax
<type>
<identifier>
= value literal
| new <custom_type>(...)
;
Examples
int age = 4;
Person Owner = new Person("Mohamed");
WeekDays today = WeekDays.Saturday; // for enumerations
Predefined Types
Predefined types are types that are specially supported by the compiler. for example, Primitive types.
Instantiation
Predefined types can be instantiated by using a literal. for example, 12
or "Hello Wordl!"
.
int age = 26;
string name = "Mohamed Halawa"
Taxonomy
The predefined types in C# are as follows:
Value types
- Numeric
- Signed integer (sbyte, short, int, long)
- Unsigned integer (byte, ushort, uint, ulong)
- Real number (float, double, decimal)
- Logical (bool)
- Character (char)
Reference Types
- String (string)
- Object (object)
Predefined types in C# alias .NET types in the System namespace. There is only a syntactic difference between these two statements:
int i = 5;
System.Int32 i = 5;
The set of predefined value types excluding decimal are known as primitive types in the CLR.
Primitive types are so called because they are supported directly via instructions in compiled code, and this usually translates to direct support on the underlying processor; for example:
Custom Types
A user can define his own data type to accommodate his needs. for example, creating a class type to model a person:
class Person
{
string _name;
public Person(string name){
this._name = name;
}
}
Person Owner = new Person("Mohamed Halawa");
Instantiation
Custom types defined by class
, struct
keywords can be instantiated using the new
Operator.
The
new
Operator creates instances of a custom type.
- Immediately after the
new
operator instantiates an Object, the object’s constructor is called to perform initialization. - A Constructor is defined like a method, except the method name and return type are reduced to the name of the enclosing type.
public Person( string name ){ this._name = name; }
Instance vs Static Members
Instance Members
- The data members and function members that operate on the instance of the type are called instance members.
Static Members
- Data members and function members that don’t operate on the instance of the type can be marked as static.
- To refer to a static member from outside its type, you specify its type name rather than an instance.
Type Conversion
C# can convert between instances of compatible types. A conversion always creates a new value from an existing one.
Conversion Types
- Implicit conversion
- Explicit conversion
If the compiler can determine that a conversion will always fail, both kinds of conversion are prohibited.
Implicit Conversion
Implicit conversions are allowed when both of the following are true
:
- The compiler can guarantee that it will always succeed.
- No Information is lost in the conversion.
Implicit conversion always happen automatically.
Because long
type has a bigger storage capacity (64-bit) than int type (32-bit), the conversion from int
to long
happens Implicitly.
int x = 12345;
long y =x ;
Explicit Conversion
Explicit conversions are required when one of the following is true:
- The compiler cannot guarantee that it will always succeed.
- Information might be lost during conversion.
Explicit conversions require a cast.
Because short
the type has a smaller storage capacity (16-bits) than int type (32-bit), the conversion from int
to long
happens explicitly and requires a cast.
int x = 12345;
short y =(short)x ;
Additional Notes
- The numeric conversions are built into the language.
- The compiler doesn’t enforce the aforementioned rules with custom conversions, so it’s possible for badly designed types to behave otherwise.
- Conversions that involve generics can also fail in certain conditions.
A minor caveat is that very large long values lose some precision when converted to double.
Value Types Versus Reference Types
All C# types fall into the following categories:
- Value types
- Reference types
- Generic type parameters
- Pointer types
The fundamental difference between value types and reference types is how they are handled in memory.
Value types
Value types comprise most built-in types specifically :
- Numeric types
- Char type
- Bool type
- Custom struct
- Enum types
- The content of a value-type variable or constant is simply a value. For example, the content of the built-in value type,
int
, is 32-bit of data. - You can define custom value type with the struct keyword
public struct Point {int X,Y;}

The assignment of a value-type instance always copies the instance; for example:
Point p1 = new Point();
p1.X= 7;
Point p2 = p1; // Assignment causes copy
Console.WriteLine(p1.X); // 7
Console.WriteLine(p2.X); // 7
p1.X = 9; // Changing the original value won't affect the copied one.
Console.WriteLine(p1.X); // 9
Console.WriteLine(p2.X); // 7

Storage Overhead
Value-type instances occupy precisely the memory required to store their fields, for example:
// Any instance of the struct will occupy 8 bytes of memory
struct Point{
int x; // 4bytes
int y; // 4bytes
}
Technically, the CLR positions fields within the type at an address that’s a multiple of the fields’ size (up to a maximum of 8 bytes).
For example, the following will consume 16 bytes of memory with the 7 bytes following the first field wasted.
// Total 9 bytes
// CLR will position the fields into 16bytes (multiple of 8)
// the remaining 7 bytes (16 - 9) will be wasted !
struct A{
byte b; // 1 byte
long l; // 8 byte
}
You can override this behaviour by applying the
StructLayout
attribute.
Reference types
Reference types comprise all :
- Array
- String
- Class
- Delegate
- Interface types
Assigning a reference-type variable copies the reference, not the object instance. This allows multiple variables to refer to the same object—something not ordinarily possible with value types.
If we repeat the previous example, but with Point now a class, an operation to p1 affects p2:
Point p1 = new Point();
p1.X = 7;
Point p2 = p1; // Copies p1 reference
Console.WriteLine (p1.X); // 7
Console.WriteLine (p2.X); // 7
p1.X = 9; // Change p1.X
Console.WriteLine (p1.X); // 9
Console.WriteLine (p2.X); // 9

Storage Overhead
- Reference types require separate allocations of memory for the reference and object.
- The object consumes as many bytes as its fields, plus additional administrative overhead.
- The precise overhead is intrinsically private to the implementation of the .NET runtime, but at minimum, the overhead is 8 bytes, used to store a key to the object’s type as well as temporary information such as its lock state for multithreading and a flag to indicate whether it has been fixed from movement by the garbage collector.
- Each reference to an object requires an extra 4 or 8 bytes, depending on whether the .NET runtime is running on a 32-bit or 64-bit platform.
Null
A reference can be assigned the literal null, indicating that the reference points to no object.
Point p = null;
Console.Writeline(p == null); // True
// The following line generates a runtime error
// (a NullReferenceException is thrown):
Console.WriteLine(p.X);
class Point{...}
In contrast, a value type cannot ordinarily have a null value:
Point p = null; // Compile-time error
int x = null; // Compile-time error
struct point {...}
C# also has a construct called nullable value types for representing value-type nulls.