5 things you didn't know about Guid in C#

January 21, 20205 Min Read
I'm pretty sure that you've already used Guids in C#, but have you ever stopped to think what they are under the hood?

I'm pretty sure that you've already used Guids in C#, but have you ever stopped to think what they are under the hood?

#1: Guids have a fixed size

A GUID is a 128-bit integer (16 bytes) value. That means that there are more than 300,000,000,000,000,000,000,000,000,000,000,000,000 different values. A big number, isn't it?

It is virtually impossible to have duplicates, so it is safe to use.

Notice that an unsigned long is made of 64 bits: the biggest integral value that we can have has half of the bits of a Guid. The only type with the same size is decimal, but here we must consider both the sign and the precision.

#2: Guid is a struct

Just like int and short, a Guid is a struct and not an object.

public struct Guid : IComparable, IComparable, IEquatable, IFormattable

Since this is a value type, if we pass it to a method, it won't change its value:

void Main()
{
    var initialGuid = Guid.NewGuid();
    Console.WriteLine("Before: "+ initialGuid);

    updateGuid(initialGuid);
    Console.WriteLine("After: "+ initialGuid);
}

void updateGuid(Guid tmpGuid){
    tmpGuid = Guid.NewGuid();
}

will print

Before: d7241bf7-2778-42a9-a2e2-99228ada8c54
After: d7241bf7-2778-42a9-a2e2-99228ada8c54

But, if we use the ref keyword

void Main()
{
    var initialGuid = Guid.NewGuid();
    Console.WriteLine("Before: "+initialGuid);

    updateGuidRef(ref initialGuid);
    Console.WriteLine("AfterRef: "+initialGuid);
}

void updateGuidRef( ref Guid tmpGuid)
{
    tmpGuid = Guid.NewGuid();
}

we will have

Before: f93239da-4d20-4cb9-a8b7-df9002e4a042
AfterRef: b4274547-089b-42c9-a2d1-5d4d3a62f37a

#3: You can create a Guid

For sure, the typical way of creating a Guid is using the static method Guid.NewGuid(). There are other ways to generate them.

If you want to create an empty Guid, you can use Guid.Empty: it will return a Guid composed only by 0s, like 00000000-0000-0000-0000-000000000000. Since we are talking about a struct, it doesn't make sense to have a null value, of course!

If you already have a GUID stored as string, you can parse it with Guid.Parse and Guid.TryParse. Just like for DateTime and for integers, the first one works only if the input string has a valid value, the second one tries to parse a value and assign it to a variable.

var guid1 = Guid.Parse("fc072692-d322-448b-9b1b-ba3443943579");
Console.WriteLine("Guid1: " + guid1);

Guid.TryParse("fc072692-d322-448b-9b1b-ba3443943579", out var guid2);
Console.WriteLine("Guid2: "+guid2);

You can also use the simple constructor, like

var guid = new Guid("fc072692-d322-448b-9b1b-ba3443943579");

or some of the more advanced constructors that operate at low level: for example, you can use a byte array as an input to the constructor, and have it converted to Guid. Of course, the array must be of 16 bytes.

var bytes = new byte[16];
var guid = new Guid(bytes); // 00000000-0000-0000-0000-000000000000

#4: A Guid has multiple formats

Now that you know that a Guid is made of 16 bytes, you can think "are the hyphens part of those bytes?".

Well, no: those are part of the default string representation of a Guid.

When using the ToString() method you can specify the format that you want. There are different types:

  • D: 32 digits, but with the hyphens. This is the default
  • N: 32 digits, without any other symbols
  • B: here we have the hyphens, and the string is enclosed in braces
  • P: similar to B, but with parentheses instead of braces
  • X: here we have the hexadecimal representation of the guid.

If we try to print the same Guid with the different formats, we can have something like

var tmpGuid = Guid.NewGuid();
Console.WriteLine("D \t"+tmpGuid.ToString("D"));
Console.WriteLine("N \t"+tmpGuid.ToString("N"));
Console.WriteLine("B \t"+tmpGuid.ToString("B"));
Console.WriteLine("P \t"+tmpGuid.ToString("P"));
Console.WriteLine("X \t"+tmpGuid.ToString("X"));

that will print

D   e10deb88-171b-4c34-81f7-05fc17d16316
N   e10deb88171b4c3481f705fc17d16316
B   {e10deb88-171b-4c34-81f7-05fc17d16316}
P   (e10deb88-171b-4c34-81f7-05fc17d16316)
X   {0xe10deb88,0x171b,0x4c34,{0x81,0xf7,0x05,0xfc,0x17,0xd1,0x63,0x16}}

Do you remember the Guid.Parse method that I showed before? Well, there is a secret sibling! Guid.ParseExact converts a string into a Guid only if it has the expected format.

So

Guid.ParseExact("(e10deb88-171b-4c34-81f7-05fc17d16316)", "P");

will work, but

Guid.ParseExact("(e10deb88-171b-4c34-81f7-05fc17d16316)", "N");

and

Guid.ParseExact("{e10deb88-171b-4c34-81f7-05fc17d16316}", "P");

won't.

#5: Guids have NOT a fixed size

As I said, a Guid takes 16 bytes. So it's easy to suppose that sizeof(Guid) will return 16.

Well... no! It doesn't even compile, because _'Guid' does not have a predefined size; therefore you can use sizeofonly in an unsafe context.

That's because the size of a Guid is constant, but the memory allocated by the CLR isn't necessary constant (because for some architecture it can add a padding at the end, at the beginning or within the allocated memory).

So, you can see the value in 2 ways: using the unsafe operator

unsafe
{
    sizeof(Guid);
}

or using the Marshal.SizeOf method from System.Runtime.InteropServices.

Marshal.SizeOf()

Wrapping up

Not so boring, isn't it? For sure, this is a great functionality of C#, and I hope I've triggered your curiosity about the hidden aspects of this language.

Happy coding!

Hey, what do you think of this article?
Let me know on Twitter!

Of course, if you have suggestions on some topics you'd like to read about, just ask!