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 sizeof
only 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!
Previously seen at Code4IT
Top comments (5)
God Job, I congratulate you for this post. Sharing and collect knowledge is important. Most of the times We tend to believe that everything is obvious and that everybody is going to understand what they are using, but the true is that is not the case. This is going to save tons of time and headaches to many people.
Exactly! That's the point! When we do something that we think is simple, we forget about the complexity behind it. Sometimes it's fun to dive into details of something we use every day!
I am going to add a small detail, that hopefully will prevent headaches for people that work with Guids and ByteArrays.
Careful when you use Guid.ToByteArray the order of bytes is different from the string representation of the string value. Specifically if you want to create one with the same order you need to do the following (simplified)
For context, one should also note the entire industry just uses the term UUID to define a similar thing. In normal fashion, Microsoft went off and renamed the things and added some object goo in .NET.
You can also, in .NET or other languages pull or build a specific UUID version too. Because they are specific and were made with different contexts. For example, with Go check out these options...
compositecode.blog/2018/05/14/uuid...
More details include the Microsoft claim on their use as synonyms within the IETF TFC 4122:
In the ITU-T Recommendation X.667:
MS claiming a GUID is specified by the UUID RFC: "In Microsoft Windows programming and in Windows operating systems, a globally unique identifier (GUID), as specified in [RFC4122], is ... The term universally unique identifier (UUID) is sometimes used in Windows protocol specifications as a synonym for GUID."
Just one of those Microsoft things. ;)
Essential knowledge of GUID. Well done