Guide to C# Data Types, Variables and Object Casting

C# supports all the major data types and a whole load more. This list has the most common types and ranges/lengths of values they can hold.

2,702 words, estimated reading time 10 minutes.
Introduction to Programming with C#

This article is part of a series of articles. Please use the links below to navigate between the articles.

  1. Learn to Program in C# - Full Introduction to Programming Course
  2. Introdution to Programming - C# Programming Fundamentals
  3. Introduction to Object Oriented Programming for Beginners
  4. Introduction to C# Object-Oriented Programming Part 2
  5. Application Flow Control and Control Structures in C#
  6. Guide to C# Data Types, Variables and Object Casting
  7. C# Collection Types (Array,List,Dictionary,HashTable and More)
  8. C# Operators: Arithmetic, Comparison, Logical and more
  9. Using Entity Framework & ADO.Net Data in C# 7
  10. What is LINQ? The .NET Language Integrated Query
  11. Error and Exception Handling in C#
  12. Advanced C# Programming Topics
  13. All About Reflection in C# To Read Metadata and Find Assemblies
  14. What Are ASP.Net WebForms
  15. Introduction to ASP.Net MVC Web Applications and C#
  16. Windows Application Development Using .Net and Windows Forms
  17. Assemblies and the Global Assembly Cache in C#
  18. Working with Resources Files, Culture & Regions in .Net
  19. The Ultimate Guide to Regular Expressions: Everything You Need to Know
  20. Introduction to XML and XmlDocument with C#
  21. Complete Guide to File Handling in C# - Reading and Writing Files

Like most modern programming languages, C# supports all the major data types and a whole load more. This page lists the most common types and the ranges/lengths of values they can hold.

All minimum and maximum values can be found using (data type).MinValue and (data type).MaxValue (e.g. int.MinValue).

C# Common Data Types and Ranges

Type Bytes Description Minimum Maximum Example
bool 1 Named literal false true
sbyte 1 Signed byte -128 127
byte 1 Unsigned byte 0 255
short 2 Signed short integer -32768 32767
ushort 2 Unsigned short 0 65535
int 4 Signed integer -2147483648 2147483647
uint 4 Unsigned integer 0 4294967295
long 8 Signed long int -9.2233E+18 9.2233E+18
ulong 8 Unsigned long int 0 18446E+19
char 2 Unicode character, contained within single quotes. 0 128 a,b,4
float 4 floating point -3.402823E+38 3.402823E+38 3.14159
double 8 Floating point -1.7976931E+308 1.7976931E+308 3.14159
decimal 16 Floating point, accurate to the 28th decimal place. -7.9228E+24 7.9228E+24
object 8+ Base type for all other types n/a n/a n/a
string 20+ Immutable character array n/a n/a "Hello World"
DateTime 8 Represents an instant in time, typically expressed as a date and time of day. 00:00:00 01/01/0001 23:59:59 31/12/9999 14:289:35 08/05/2010

Working with Dates & Time

The first step in using the DateTime object when working with dates and times in C# is to create an instance of the DateTime class. This is done using the new keyword passing in the year, month and day values.

C#
DateTime christmas = new DateTime(25, 12, 2018);

When we get the value of the DateTime we get:

C#
string dateAsString = christmas.ToString();

The value of dateAsString is now "25/12/2018 12:00:00 AM". This will depend on your system locale and date settings.

We can also specify the time when creating a DateTime. This is done by adding hours, minutes and seconds to the constructor.

C#
DateTime christmas = new DateTime(25, 12, 2018, 15, 30, 55);

Now this will have a value of "25/12/2018 3:30:55 PM".

You can access the current date and time by using DateTime.Now.

Adding or Subtracting Time

Let's say you wanted to get the date 30 days from now. You can use the AddDays method for this.

C#
DateTime date30DaysTime = DateTime.Now.AddDays(30);

You can do the same to get the date 30 days in the past, simply pass a negative number to subtract days.

C#
DateTime date30DaysTime = DateTime.Now.AddDays(-30);

You can do the same for years, months, days, hours, minutes and seconds.

Difference Between Two DateTimes

You can calculate the distance between two DateTimes by doing the following.

C#
TimeSpan duration = dateTime1 - dateTime2;

The TimeSpan class has many properties you can access to get the hours, minutes and seconds between the two DateTimes.

C#
duration.TotalSeconds
duration.TotalMinutes
duration.TotalHours

Date/Time Format Strings

Date/Time formats are dependent on the user's locale, so the output may be different. These can be passed into the ToString method as well as string.Format method.

C#
string date = DateTime.Now.ToString("d");
string date = string.Format("{0:d}", DateTime.Now);
Character Description Usage Example Output
d Short date {0:d} 08/12/2007
D Long date {0:D} 08 December 2007
t Short time {0:t} 15:27
T Long time {0:T} 15:27:40
f Long date time {0:f} 08 December 2007 15:27
F Long date time {0:F} 08 December 2007 15:27:40
g Short date time {0:g} 08/12/2007 15:27
G Short date time {0:G} 08/12/2007 15:27:40
M Short date {0:M} 08 December
r RFC1123 Date time string {0:r} Sat, 08 Dec 2007 15:27:40 GMT
s Sortable date/time {0:s} 2007-12-08T15:27:40
u Universal sortable date {0:u} 2007-12-08 15:27:40
U Universal full date {0:U} 08 December 2007 15:27:40
Y Year month pattern {0:Y} December 2007

Enums and Flags

An enum or enumerate is a set consisting only of defined constants. They are useful for limiting the values that a type can contain.

C#
enum daysInWeek {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday};

Each item in the set is assigned a numerical value, in this example, Monday equals 1 and Sunday = 7. You can specify your own values using the equal's symbol followed by a numeric.

C#
enum daysInWeek {Monday=0, Tuesday=2, Wednesday=4, Thursday, Friday, Saturday, Sunday};

Values that have not been explicitly assigned a value take the value of the previous item plus one, thus Thursday will equal 5.

Enums can be used as constants to ensure that only the specified values are allowed.

C#
int day = daysInWeek.Wednesday;

if (day == daysInWeek.Thursday)
{
  Console.WriteLine("Today is Thursday");
}

Enums can also be used to define a binary set, which is a value that holds various options about something using the [Flags] attribute. These are useful where there are many boolean options that an object can have and cut down on the number of properties that a call will have.

C#
bool hasButtons = true;
bool hasZip = false;
bool hasPockets = false;
bool hasEmbrodery = true;

Rewritten using enum bit flags gives:

C#
[Flags]
public enum garmentOptions
{
  hasButtons = 0x01,   / 00000001  1
  hasZip = 0x02,       / 00000010  2
  hasPockets = 0x04,   / 00000100  4
  hasEmbrodery = 0x08; / 00001000  8
}

garmentOptions myGarment = garmentOptions.hasButtons | garmentOptions.hasEmbrodery;

/ result of myGarment is 9 or:
/   00000001  1 - hasButtons 
/ + 00001000  8 - hasEmbrodery
/ = 00001001  9

if ((myGarment & garmentOptions.hasEmbrodery) > 0)
{
  / Embrodery specific code here
}

The logical bitwise & is used to test if an enum bit flag is part of the set in myGarment.

C# Nullable Types

Nullable types can represent all the values of an underlying type T, and an additional null value.

Nullable types are used when you need to represent the undefined value of an underlying type. For example, a boolean variable can only have true and false values. There is no "undefined" value. In many programming applications, most commonly database interactions, a variable value can be undefined or missing. For example, a field in a database may contain the values true or false, or it may contain no value at all. You use a Nullable type in that case.

Nullable can be defined in one of two ways. The second is the shorthand version of the first.

C#
Nullable<int> i = null;
int? i = null;

To check in a nullable has a value you can use the HasValue property. If you try to access the value without checking if it has a value you will get an exception thrown.

C#
static void Main()
{
  int? i = null;

  if (i.HasValue)
    Console.WriteLine(i.Value);
  else
     Console.WriteLine("Null");
}

You can also use the GetValueOrDefault() method to get an actual value if it is not null and the default value if it is null. For example:

C#
static void Main()
{
    int? i = null;

    Console.WriteLine(i.GetValueOrDefault()); 
}

C# Constants and Read-Only Variables

C# Constants and Read Only variables perform the same task, the only difference between them is that a constant is given a value at compile time and cannot ever be changed. Read Only variables can be assigned to once at runtime, but thereafter cannot be changed.

Constants

Constants are declared using the const keyword and can be used as class members or as method values and can be marked with any access modifier apart from static. Constants should be used anywhere where you would hard code a value, such as several results to return, literal strings, loop control, mathematical constants, fixed costs and so on.

C#
public const decimal pi = 3.14M;
private const int numRows = 10;
private const decimal vatRate = 17.5M;

This will define a value for pi which will never change once the program is compiled; similarly, numRows will be initialised to 10 and will never change. If you try to assign to a const value the compiler will throw an error.

Read Only

Read-only values allow one assignment at runtime and can be used to hold information that needs to be read in or processed. Once a read-only value has been assigned to once it cannot be assigned to again until the application is restarted.

C#
public class SomeTestClass
{
  private readonly string filePath;

  public SomeTestClass(string filename)
  {
    filePath = Path.GetDirectoryName(filename);
  }
}

This will initialise the value filePath to the directory of the specified filename. Once the constructor has been assigned to filePath it can no longer be assigned to.

Using Consts and Read Only Values

Let's assume you are creating a class to deal with calculations involving circles. You create the class using hard-coded values for pi.

C#
public class Circles
{
  public double Circumference(double radius)
  {
    return circumference= 3.14 * (radius * 2);
  }

  public double Area(double radius)
  {
    return area = 3.14 * Math.Pow(radius, 2);
  }

  public double AnotherTest(double radius)
  {
    if (radius <= 3.14)
      return radius * 3.14 * Math.Pow(3.14, 3);
    else 
      return radius * Math.Pow(3.14, 4);   
  }
}

You can imagine that this class could contain many more methods and constants. During testing, it has been found that the calculations performed do not have the accuracy required. It is necessary to improve accuracy by changing the value of pi from 3.14 to 3.1415926. How do you accomplish this?

You could do a search and replace, but how do you know that 3.14 is always the value of pi? The AnotherTest method does not use pi, but another fixed value that has the same value. Changing this value could result in undesired results.

It would be much better to have created the class using constants which would have the following benefits:

  1. The value of pi needs only to be changed once.
  2. You can be sure that you haven't missed any values.
  3. The code looks neater and is easier to understand.
  4. pi value 3.14 is not confused with another value of 3.14
C#
public class Circles
{
  private const double pi = 3.1415926;
  private const double test = 3.14;

  public double Circumference(double radius)
  {
    return circumference= pi * (radius * 2);
  }

  public double Area(double radius)
  {
    return area = pi * Math.Pow(radius, 2);
  }

  public double AnotherTest(double radius)
  {
    if (radius <= test)
      return radius + test * Math.Pow(pi, 3);
    else 
      return radius * Math.Pow(pi, 4);   
  }
}

From this, you can see that it is easy to see that the values for test and pi are totally different in the AnotherTest method. Also, to change the value of pi, simply change one line of code at the top of the class.

Of course in the real world, you would use Math.PI for the value of pi as this figure is an even more accurate predefined constant.

Implicitly Typed Variables

Local variables can be declared without giving an explicit type. The var keyword instructs the compiler to infer the type of the variable from the expression on the right side of the initialization statement. This is only possible inside a method, not at the class level.

C#
int age = 37 / Explicitly typed variable

var name = "Tim Trott"; / Implicitly typed variable

There are a few important points to consider when using an implicitly typed variable. Firstly It is mandatory to assign an initial value to the implicitly typed variable. Secondly, implicit variables just mean that the compiler will determine the type of the variable at compile time.

Generic Types

C# Generics make it possible to design classes and methods that do not specify data types until the class or method is declared and instantiated by client code.

By using a generic type parameter you can write a single class that other client code can use without incurring the cost or risk of casts or boxing operations.

In this first example, we can see how a method can accept an array of various types and output the values. Without generics you would need to override the method for each data type, causing problems for scalability and custom types.

C#
using System;
using System.Collections.Generic;

class Generics
{
    static void Main(string[] args)
    {
        / create arrays of various types
        int[] intArray = { 1, 2, 3, 4, 5, 6 };
        double[] doubleArray = { 1.0, 2.0, 3.0, 4.0, 5.0 };
        char[] charArray = { 'A', 'B', 'C', 'D', 'E' };

        DisplayArray(intArray);
        DisplayArray(doubleArray);
        DisplayArray(charArray);

        Console.ReadLine();
    }

    / generic method displays array of any type
    static void DisplayArray<E>(E[] array)
    {
        Console.WriteLine("Display array of type " + array.GetType() + ":");
        foreach (E element in array)
            Console.Write(element + " ");
    }
}

You can see how the one method will handle requests for an array of ints, doubles and chars. We can also use reflection and the GetType method to determine the type of array passed as the generic parameter.

Anonymous Types

Anonymous types provide a convenient way to encapsulate a set of read-only properties into a single object without having to explicitly define a type first. The type name is generated by the compiler and is not available at the source code level. The type of each property is inferred by the compiler.

The following example shows an anonymous type that is initialized with two properties named Name and Age.

C#
var result = new { Name = "Tim Trott", Age = 37 };

Anonymous types typically are used in the select clause of a query expression to return a subset of the properties from each object in the source sequence. For more information about queries, see the tutorial on LINQ Query Expressions.

Dynamic Types

Dynamic type is the same as static type, but an object of type dynamic bypasses static type checking. At compile time, an element that is typed as dynamic is assumed to support any operation.

A dynamic type changes its type at runtime based on the value of the expression to the right of the "=" operator.

A dynamic type can be defined using the dynamic keyword.

C#
dynamic dynamicVariable = 1;

The following example shows how a dynamic variable changes its type based on its value:

C#
static void Main(string[] args)
{
    dynamic dynamicVariable = 100;
    Console.WriteLine("Dynamic variable value: {0}, Type: {1}",dynamicVariable, dynamicVariable.GetType().ToString());

    dynamicVariable = "Hello World!!";
    Console.WriteLine("Dynamic variable value: {0}, Type: {1}", dynamicVariable, dynamicVariable.GetType().ToString());

    dynamicVariable = true;
    Console.WriteLine("Dynamic variable value: {0}, Type: {1}", dynamicVariable, dynamicVariable.GetType().ToString());

    dynamicVariable = DateTime.Now;
    Console.WriteLine("Dynamic variable value: {0}, Type: {1}", dynamicVariable, dynamicVariable.GetType().ToString());
}

Type Casting

Some data types can be assigned to a different type if they can be implicitly cast. This means that a small number can be assigned to a large number, but not vice versa.

Let's have a look at two data types, the byte and the long. As you can see from the data types guide, a byte can hold whole numbers between 0 and 255 and a ulong can hold whole numbers between 0 and 9,223,372,036,854,775,807. Think of these as a shot glass and a pint glass.

C#
  byte shotGlass;
 ulong pintGlass;

In these analogies, please try and imagine that when we "pour" one glass into another glass, the original glass does not lose any of its contents. Rather, an equal amount of fluid is added to the other glass, while the original retains its contents.

Implicit Casting

It makes sense that you can pour the contents of the shot glass into the pint glass; there is no danger of it overflowing, so the compiler will allow this to happen, without any warnings. This is called an implicit typecast and is done by the compiler automatically each time you assign a variable to another variable.

C#
  pintGlass = shotGlass;

The pintGlass now contains the contents of the shotGlass.

Explicit Casting

So what happens if you want to pour the contents of the pint glass into the shot glass? It may be possible, it depends on how much is in the pint glass. The compiler will see this as dangerous as there is a good chance that the shot glass will overflow, so it will flag an error and prevent the program from compiling.

C#
 shotGlass = pintGlass;

Cannot implicitly convert type 'ulong' to 'byte'. An explicit conversion exists (are you missing a cast?)

It is possible however to say to the compiler, "I know what I am doing, please let me pour the contents of my pint glass into my shot glass." This is called an explicit typecast and is performed using the data type you are converting to in brackets just before the variable.

C#
  shotGlass = (byte) pintGlass;

In this case, the compiler assumes we know what we are doing and allows it.

If the shot glass does overflow, the value of the shotGlass will roll over and start from 0, so if you try and put a value of 256 into the shotGlass, the value would be 0, a value of 257 would be 1, 258 would be 2 and so on.

You should only explicitly typecast when you are certain that the values will fit, and it is sensible to test for this condition before performing the conversion.

Floating Point

Floating-point numbers can store values with a decimal fraction as well as a value, for example, 2.5632. You can implicitly cast an int to a float or decimal type, but you cannot implicitly cast a decimal or float to an int as an int will not be able to hold the data after the decimal point. You can explicitly cast, however, you will lose any decimal data, i.e. 2.5632 cast as an int will become just 2.

Was this article helpful to you?
 

Related ArticlesThese articles may also be of interest to you

CommentsShare your thoughts in the comments below

If you enjoyed reading this article, or it helped you in some way, all I ask in return is you leave a comment below or share this page with your friends. Thank you.

This post has 1 comment(s). Why not join the discussion!

We respect your privacy, and will not make your email public. Learn how your comment data is processed.

  1. TZ

    On Monday 28th of March 2016, Timothy M. Zonoe said

    Great Resources...Please, I'll be glad if you can update me with new stuff all the time.