11

I just found an interesting problem between translating some data:

VB.NET: CByte(4) << 8 Returns 4

But C#: (byte)4 << 8 Returns 1024

Namely, why does VB.NET: (CByte(4) << 8).GetType() return type {Name = "Byte" FullName = "System.Byte"}

Yet C#: ((byte)4 << 8).GetType() returns type {Name = "Int32" FullName = "System.Int32"}

Is there a reason why these two treat the binary shift the same? Following from that, is there any way to make the C# bit shift perform the same as VB.NET (to make VB.NET perform like C# you just do CInt(_____) << 8)?

Seph
  • 8,472
  • 10
  • 63
  • 94
  • It's an interesting difference, but do you really need that wrapping-shift-on-a-byte-only behaviour? It should be easy to modify your code that's using it – Rup Nov 16 '11 at 12:05
  • 1
    Interesting; I am familiar with the wrapping of c# with 32/64 bits, but didn't realise that VB took this metaphor further to 8-bit wrapping on bytes. Curious! – Marc Gravell Nov 16 '11 at 12:13
  • @MarcGravell: Looks like its just because C# doesn't work with bytes or shorts and just implicitly converts them to ints. I think in this case VB has the right idea but I suspect its something to do with how C++ defines the operator or something that defined the C# thinking (see my answer for links to sources). – Chris Nov 16 '11 at 12:20
  • +1, for a nice question. – Sai Kalyan Kumar Akshinthala Nov 16 '11 at 13:16

3 Answers3

11

According to http://msdn.microsoft.com/en-us/library/a1sway8w.aspx byte does not have << defined on it for C# (only int, uint, long and ulong. This means that it will use an implciit conversion to a type that it can use so it converts it to int before doing the bit shift.

http://msdn.microsoft.com/en-us/library/7haw1dex.aspx says that VB defines the operation on Bytes. To prevent overflow it applies a mask to your shift to bring it within an appropriate range so it is actually in this case shifting by nothing at all.

As to why C# doesn't define shifting on bytes I can't tell you.

To actually make it behave the same for other datatypes you need to just mask your shift number by 7 for bytes or 15 for shorts (see second link for info).

Chris
  • 27,210
  • 6
  • 71
  • 92
  • I couldn't decide which to accept so I picked the one that was answered first, all three are good answers. It seems C# does it because C does it this way (and C# spec is heavily modelled on C), and VB.NET does it different because VB did it different, as to why VB did it that way reason is probably obscure or lost. While they're both compile to .NET CLR they are very different languages, so this is just another thing to look out for along side array lengths (and others). – Seph Nov 17 '11 at 08:07
6

To apply the same in C#, you would use

static byte LeftShiftVBStyle(byte value, int count)
{
    return (byte)(value << (count & 7));
}

as for why VB took that approach.... just different language, different rules (it is a natural extension of the way C# handles shifting of int/&31 and long/&63, to be fair).

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • And the C# convention most likely stems from the way x86 handles shifts. One more example where a stupid design decision at the dawn of time stays forever. – CodesInChaos Nov 16 '11 at 12:57
  • @Code - That dawn of time probably started at Ken Thompson's PDP-8. The vb.net way is actually highly compatible with x86 shifts which can handle byte and short sized registers. – Hans Passant Nov 16 '11 at 13:10
6

Chris already nailed it, vb.net has defined shift operators for the Byte and Short types, C# does not. The C# spec is very similar to C and also a good match for the MSIL definitions for OpCodes.Shl, Shr and Shr_Un, they only accept int32, int64 and intptr operands. Accordingly, any byte or short sized operands are first converted to int32 with their implicit conversion.

That's a limitation that the vb.net compiler has to work with, it needs to generate extra code to make the byte and short specific versions of the operators work. The byte operator is implemented like this:

Dim result As Byte = CByte(leftOperand << (rightOperand And 7))

and the short operator:

Dim result As Short = CShort(leftOperand << (rightOperand And 15))

The corresponding C# operation is:

Dim result As Integer = CInt(leftOperand) << CInt(rightOperand)

Or CLng() if required. Implicit in C# code is that the programmer always has to cast the result back to the desired result type. There are a lot of SO questions about that from programmers that don't think that's very intuitive. VB.NET has another feature that makes automatic casting more survivable, it has overflow checking enabled by default. Although that's not applicable to shifts.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536