/** * Write(); }
All I had to do to convert the C# was to change all managed objects to managed pointers (String in C# is String* in C++.) I added a using namespace directive to keep this code shorter:
using namespace System::IO;
The VB block was harder to convert. I added the reference to Microsoft.VisualBasic.dll and made all the necessary rearrangements for the language syntax (Dim i As Integer becomes int i;) but that wasn't the end of it. The optional parameters in the VB definition of the function aren't optional from C++, so I had to look in the online help for the default values of those parameters and pass them in. What's more, PrintLine doesn't take a string as its second parameter; it takes an array of Object references. I had to create an array and then set the first element to the string I wanted to print. (If you ever have to write C++ code to call a .NET Base Class Library method that takes an array of objects, remember this sample.) The same thing happened with FileClose, which takes an array of integers, not just one integer. Here's how the VB code looked when the conversion to C++ was complete:
String* fileName = "C:\\TestFileOpen.txt"; String* textLine = _ "abcdefghijklmnopqrstuvwxyz1234567890 _ abcdefghijklmnopqrstuvwxyz1234567890abcdefgh"; String* Line = ""; Microsoft::VisualBasic::FileSystem::FileOpen(1, fileName, Microsoft::VisualBasic::OpenMode::Output, Microsoft::VisualBasic::OpenAccess::Default, Microsoft::VisualBasic::OpenShare::Default,-1); String* array __gc[]= { textLine }; int i=0; while (i < ioMax) { Microsoft::VisualBasic::FileSystem::PrintLine(1, array); i += 1; } int handle __gc[]= { 1 }; Microsoft::VisualBasic::FileSystem::FileClose(handle); Microsoft::VisualBasic::FileSystem::FileOpen(2, fileName, Microsoft::VisualBasic::OpenMode::Input, Microsoft::VisualBasic::OpenAccess::Default, Microsoft::VisualBasic::OpenShare::Default,-1); i = 0; while (i < ioMax) { Line = Microsoft::VisualBasic::FileSystem::LineInput(2); i += 1; //Console::WriteLine(" i: {0}", __box(i)); //Console::WriteLine(" Line: {0}", Line); } handle[0] = 2; Microsoft::VisualBasic::FileSystem::FileClose(handle);
A using namespace directive here would make things simpler but I want to emphasize that this C++ code is using the VB library. It's weird, but you can do it. I'm about to show you why you don't want to.
I ran this code repeatedly, a Release build, and I ignored the first few runs to be sure that all the code was JITted, and that anything likely to be cached was cached. After jumping around a bit at first, it settles into a steady pattern: The originally VB code takes about seven times as long as the originally C# code. The difference isn't the language, it's the library—and it's a big difference!
Another pattern you're likely to see in originally VB code is references set to Nothing instead of a call to Close() or Dispose(), often with horrendous performance consequences. Does that mean VB.NET is a slow language? Of course not. It does mean that VB 6 programmers need to learn what to do to write high-performance code in VB.NET. If you knew how to use FileOpen and the other methods, why would it occur to you that you needed to go and learn a completely different technique with StreamReader and StreamWriter?
So at this point, perhaps you're thinking that C++ programmers are immune to this sort of old-style thinking that leads them to use the slow, old way instead of the shiny, new, fast way that's in the Base Class Libraries. After all, if we wanted to write to a file, couldn't we just use the ofstream and ifstream approach, with << and >>? That should be nice and fast, shouldn't it?
Well, I tried it. I found the STL way really frustrating with String* and so I redid the whole thing with char*. Here's how that looks:
char* cfileName = "C:\\TestCRT.txt"; char cLine[255]; char* ctextLine = "abcdefghijklmnopqrstuvwxyz1234567890 abcdefghijklmnopqrstuvwxyz1234567890abcdefgh"; int i = 0; ofstream of; of.open(cfileName); while (i++ < ioMax) { of << ctextLine << '\n'; } of.close(); i = 0; ifstream ifs; ifs.open(cfileName); while (i++ < ioMax) { ifs.getline(cLine,250); } ifs.close();
I bet you expect this to be the fastest of the bunch. Well, it's not! Sure, it's quicker than the FileOpen approach, but it's about three times as long as the StreamWriter. (What makes it slower? I'd have to see the insides of all these functions to be sure, but it doesn't seem to be interop transitions, because I tried pulling this whole function into another file and compiling that file as unmanaged, but didn't improve the performance.) So now, who needs to learn the new libraries to make sure their managed applications are as fast as possible?
There are two morals to this story. The first, and most important, is that benchmarking is not for the faint of heart. You have to know what you're measuring—and it isn't always what you think you're measuring. In the original article, the author reported a "difference" between two languages that was actually just a difference between the library choices some users of those languages are likely to make. When I write in VB.NET, I don't use those compatibility functions because I never did large volumes of VB 6 programming, so I don't have those habits. Don't assume all VB.NET programmers will do things the old, slow way.
The second is that the classes in System::IO are the fastest way to work with files. It's worth taking the time, when you find two ways to do the same thing, to discover which is faster. And if you only find one way? Maybe it's worth putting a little effort into seeing if there's another.