Quickly asked: Should I use unsigned integers (uint, uint8, ...) in C#?

08/07/2021

I will drop the basics about what an unsinged integer is. Let's just concentrate on the fact whether or not you should use it. The obvious case would be as Count property on your array-like structures or lists. That thing can't get smaller than zero. Let's have a look at a few things, shall we?

Common Language Specification aka CLS

Let's start with the CLS. In the CLS there is no datatypes which present unsigned values. That means if you write a library in C# where those types exist, you will get big problem in certain languages in the .NET ecosystem. But honestly who cares about CLS-compliant when >90% is C# and you are sure it will only be consumed from C# code.

Can an array have a negative length?

Well no... but technically yes. There are some reasons Array.Length is int32

  • .NET 1 only allowed objects the size of 2GB thus int32 was good enough
  • Interop with other languages needs int anyway
  • -1 as often used as a marker For example binary search returns -1 when a value wasn't found (good that -1 can't be an useful length)
  • Wrap around behavior of uint is dangerous

By the way if you want to see an negative (valid) index C/C++ comes to the rescue with pointers 😄 `

int a[3] = { 1, 2, 3 };
int* p = &a[1];  // Make p point to the second element

std::cout << p[-1];  // Prints the first element of a, equal to *(p - 1)
std::cout << p[ 0];  // Prints the second element of a, equal to *p
std::cout << p[ 1];  // Prints the third element of a, equal to *(p + 1)

Wrap around behavior of uint

uint a = uint.MaxValue  + 1;
Console.Write("a is " + a); // a is 0

What happens is basically any arithemtic behaves like a ring. Just imagine the following arithemtic operation:

uint a = 100u;
uint b = 100u;
uint c = a * b;

What really happens is that:

uint a = 100u;
uint b = 100u;
uint c = (a * b) % 2^32; // Modulo 2^32

Okay this brings big issues for example if you want to an access an array: uint arrayIndex = 0; MyArray[arrayIndex - 1]; Now you would access uint.MaxValue. Most likely there is nothing but given the probability you will run into issues. This is how uint behaves in C/C++. And this is the base for the .NET Runtime. Microsoft was at least very nice and provided us the checked keyword to not fall into this issue.

For Loops

Let's have a look at the following code-snippet:

for (unsigned int i = 9; i >= 0; i--)
{
    Console.WriteLine("Hello World");
}

How often do you think the loop will run? ... The answer is indefinitely. Why? Because of the wrap around. An uint can't get smaller than 0, therefore i >= 0 is a tautology and therefore always true.

Complex arithmetic

Just image you mix unsinged and signed in formula. Just have a look at this: Preserving rules in C. There can be tricky tricky pitfalls when mixing both.

So should I never use it?

No. There are reasons to use an uint but it should not be because of readability. In most cases int is big enough and economically.

Bit Shifting

In .NET signed and unsinged integer behave differently when it comes to bit-shifts. Signed integers doing arithemtic shifts:

arithemtic shift

And unsigned integers do logical shifts:

logical shift

If you need logical bitshifts you would take uints.

Interop

From personal experience I can say that a lot of stuff which is close to the hardware still uses uint. For example OpenCL or nVidia's CUDA. If you ever tried the same in JAVA it is a bit pain in the a** as Java has no unsinged data types.

Conclusion

There are cases where an uint makes sense. But in your every day life you are safer with a signed integer.

0
Buy Me a Coffee at ko-fi.com
An error has occurred. This application may no longer respond until reloaded. Reload x