In C, in contexts not involving integer promotion, unsigned values were specified to behave as members of a "wrapping" abstract algebraic ring (so for any X and Y, X-Y will yield a unique value which, when added to Y, will yield X), while signed integer types were specified as behaving like integers when computations stayed within a certain range, and allowed to do anything at all when computations went beyond that. Numerical semantics in C#, however, are totally different. When within a checked numerical context, both signed and unsigned types behave like integers provided computations stay in range, and throw
OverflowException when they don't; in an unchecked context, they both behave like algebraic rings.
The only time it's generally worthwhile to use any data type smaller than
Int32 is when it's necessary to packing or unpacking things for compact storage or transport. If one needs to store half a billion positive numbers, and they'll all be in the range 0 to 100, using one byte each rather than four will save 1.5 gigabytes of storage. That's a big savings. If a piece of code needs to store a total of a couple hundred values, however, making each of them one byte rather than four would save about 600 bytes. Probably not worth bothering with.
With regard to unsigned types, the only times they're really useful are when performing information interchange, or when subdividing numbers into pieces. If, for example, one needs to do math on 96-bit integers, it will likely be much easier to perform the computations on groups of three unsigned 32 bit integers, than on groups of signed integers. Otherwise, there aren't a whole lot of situations where the range of a signed 32- or 64-bit value would be inadequate, but the same size of unsigned value would suffice.