layout: post
title: “Built-in .NET types”
description: “Ints, strings, bools, etc”
nav: fsharp-types
seriesId: “Understanding F# types”

seriesOrder: 10

In this post we’ll take a quick look at how F# handles the standard types that are built into .NET.

Literals

F# uses the same syntax for literals that C# does, with a few exceptions.

I’ll divide the built-in types into the following groups:

  • miscellaneous types (bool, char, etc. )
  • string types
  • integer types (int, uint and byte, etc)
  • float types (float, decimal, etc)
  • pointer types (IntPtr, etc)

The following tables list the primitive types, with their F# keywords, their suffixes if any, an example, and the corresponding .NET CLR type.

Miscellaneous types











































ObjectUnitBoolChar
(Unicode)
Char
(Ascii)
Keywordobjunitboolcharbyte
SuffixB
Examplelet o = obj()let u = ()true false‘a’‘a’B
.NET TypeObject(no equivalent)BooleanCharByte

Object and unit are not really .NET primitive types, but I have included them for the sake of completeness.

String types






































String
(Unicode)
Verbatim string
(Unicode)
Triple quoted string
(Unicode)
String
(Ascii)
Keywordstringstringstringbyte[]
Suffix
Example“first\nsecond line”@”C:\name”“””can “contain”” special chars”””“aaa”B
.NET TypeStringStringStringByte[]

The usual special characters can be used inside normal strings, such as \n, \t, \\, etc. Quotes must be escaped with a backslash: \' and \".

In verbatim strings, backslashes are ignored (good for Windows filenames and regex patterns). But quotes need to be doubled.

Triple-quoted strings are new in VS2012. They are useful because special characters do not need to be escaped at all, and so they can handle embedded quotes nicely (great for XML).

Integer types































































8 bit
(Signed)
8 bit
(Unsigned)
16 bit
(Signed)
16 bit
(Unsigned)
32 bit
(Signed)
32 bit
(Unsigned)
64 bit
(Signed)
64 bit
(Unsigned)
Unlimited
precision
Keywordsbytebyteint16uint16intuint32int64uint64bigint
SuffixyuysusuLULI
Example99y99uy99s99us9999u99L99UL99I
.NET TypeSByteByteInt16UInt16Int32UInt32Int64UInt64BigInteger

BigInteger is available in all versions of F#. From .NET 4 it is included as part of the .NET base library.

Integer types can also be written in hex and octal.

  • The hex prefix is 0x. So 0xFF is hex for 255.
  • The octal prefix is 0o. So 0o377 is octal for 255.

Floating point types

































32 bit
floating point
64 bit (default)
floating point
High precision
floating point
Keywordfloat32, singlefloat, doubledecimal
Suffixfm
Example123.456f123.456123.456m
.NET TypeSingleDoubleDecimal

Note that F# natively uses float instead of double, but both can be used.

Pointer types




























Pointer/handle
(signed)
Pointer/handle
(unsigned)
Keywordnativeintunativeint
Suffixnun
Example0xFFFFFFFFn0xFFFFFFFFun
.NET TypeIntPtrUIntPtr

Casting between built-in primitive types

Note: this section only covers casting of primitive types. For casting between classes see the series on object-oriented programming.

There is no direct “cast” syntax in F#, but there are helper functions to cast between types. These helper functions have the same name as the type (you can see them in the Microsoft.FSharp.Core namespace).

So for example, in C# you might write:

  1. var x = (int)1.23
  2. var y = (double)1

In F# the equivalent would be:

  1. let x = int 1.23
  2. let y = float 1

In F# there are only casting functions for numeric types. In particular, there is no cast for bool, and you must use Convert or similar.

  1. let x = bool 1 //error
  2. let y = System.Convert.ToBoolean(1) // ok

Boxing and unboxing

Just as in C# and other .NET languages, the primitive int and float types are value objects, not classes. Although this is normally transparent, there are certain
occasions where it can be an issue.

First, lets look at the transparent case. In the example below, we define a function that takes a parameter of type Object, and simply returns it.
If we pass in an int, it is silently boxed into an object, as can be seen from the test code, which returns an object not an int.

  1. // create a function with parameter of type Object
  2. let objFunction (o:obj) = o
  3. // test: call with an integer
  4. let result = objFunction 1
  5. // result is
  6. // val result : obj = 1

The fact that result is an object, not an int, can cause type errors if you are not careful. For example, the result cannot be directly compared with the original value:

  1. let resultIsOne = (result = 1)
  2. // error FS0001: This expression was expected to have type obj
  3. // but here has type int

To work with this situation, and other similar ones, you can convert a primitive type to an object directly, by using the box keyword:

  1. let o = box 1
  2. // retest the comparison example above, but with boxing
  3. let result = objFunction 1
  4. let resultIsOne = (result = box 1) // OK

To convert an object back to an primitive type, use the unbox keyword, but unlike box, you must either supply a specific type to unbox to, or be sure that the compiler has enough information to make an accurate type inference.

  1. // box an int
  2. let o = box 1
  3. // type known for target value
  4. let i:int = unbox o // OK
  5. // explicit type given in unbox
  6. let j = unbox<int> o // OK
  7. // type inference, so no type annotation needed
  8. let k = 1 + unbox o // OK

So the comparison example above could also be done with unbox. No explicit type annotation is needed because it is being compared with an int.

  1. let result = objFunction 1
  2. let resultIsOne = (unbox result = 1) // OK

A common problem occurs if you do not specify enough type information — you will encounter the infamous “Value restriction” error, as shown below:

  1. let o = box 1
  2. // no type specified
  3. let i = unbox o // FS0030: Value restriction error

The solution is to reorder the code to help the type inference, or when all else fails, add an explicit type annotation. See the post on type inference for more tips.

Boxing in combination with type detection

Let’s say that you want to have a function that matches based on the type of the parameter, using the :? operator:

  1. let detectType v =
  2. match v with
  3. | :? int -> printfn "this is an int"
  4. | _ -> printfn "something else"

Unfortunately, this code will fail to compile, with the following error:

  1. // error FS0008: This runtime coercion or type test from type 'a to int
  2. // involves an indeterminate type based on information prior to this program point.
  3. // Runtime type tests are not allowed on some types. Further type annotations are needed.

The message tells you the problem: “runtime type tests are not allowed on some types”.

The answer is to “box” the value which forces it into a reference type, and then you can type check it:

  1. let detectTypeBoxed v =
  2. match box v with // used "box v"
  3. | :? int -> printfn "this is an int"
  4. | _ -> printfn "something else"
  5. //test
  6. detectTypeBoxed 1
  7. detectTypeBoxed 3.14