While working on a project porting a large C/C++ Unix application into the .NET world, the language requirements changed from mixed C#/VB.NET/Managed C++ to C# (and only C#). In the C/C++ sources of this project, there were many
[sf]printf statements. Migrating these to the corresponding C#
String.Format format is not only annoying, but also a little problematic. This is because
String.Format does not support all the required possibilities, as
printf does. So, what to do? The solution was to implement a
printf equivalent in C#, and that's what I will present to you in this article.
Using the code
Place a reference to the Tools assembly of the demo project, or incorporate my code into your source. Call
Tools.printf with the appropriate parameters, and you are done.
printf features a lot of conversion and formatting options -- far more than
String.Format does -- but this comes at the price that it's also more complex, in case you have never used
printf. To find out more about
printf and its possibilities, have a look at the printf man page or just Google for man printf.
It is important to note that not all possible
printf options, conversions, and formats are supported at the moment. There is also more than one
printf implementation available on the 'net! This implementation supports the most common options. It supports the l and h length modifiers, all flags such as #+- ', and the following formats: iudxXfFeEgGon. There is no difference between ASCII and Unicode strings and characters; they are always Unicode.
printf outputs to the console. It also has variations like
sprinf, which returns a formatted string, and
fprinf, which writes formatted output to a stream. It supports variable length parameters. If there are more format placeholders than actual value parameters, then placeholders without a value will be removed from the result.
Tools.printf("Account balance: %'+20.2f (%s)\n", 12345678, "great");
Building and testing the demo project
The demo project uses NUnit with test cases to test some
printf features, but not all combinations of these. So, you will need NUnit installed.
Points of interest
Converting C/C++ code with lots of
[sf]printf statements into C# is now a little easier. This
printf implementation uses Regex
\%([\'\#\-\+ ]*)(\d*)(?:\.(\d+))?([hl])?([dioxXucsfeEgGpn%]) to parse the format string and uses
String.Format internally wherever possible. Strings are processed by hand only if not otherwise possible, so the code is quite simple and small.
Some people have pointed out that it is possible to use either the underlying unmanaged Windows API functions like:
[DllImport("msvcrt.dll", SetLastError = false,
CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int sprintf(StringBuilder buff, string format, __arglist);
StringBuilder sb = new StringBuilder(256);
DateTime dt = DateTime.Now;
int hr = dt.Hour;
int mn = dt.Minute;
int sc = dt.Second;
int ml = dt.Millisecond;
MSVCRT.sprintf(sb, "%02i:%02i:%02i.%03i", __arglist(hr, mn, sc, ml));
string s = sb.ToString();
or a managed C++ wrapper called from C#:
void FormatHelper::FormatDouble(String ^%sResult, String ^sFormat, double fVal)
sStr.Format (CString(sFormat), fVal);
sResult = gcnew String(sStr);
Call it like this from C#:
string s = string.Empty;
Class1.FormatDouble(ref s, "%f6.1",132.459);
Both solutions will work, but sometimes it's not possible to use unmanaged code and other languages because of various reasons (Management - you know? ;-). That's because this is a complete rewrite of
printf in C# without any dependencies.
Thanks to Rainer Helbing, this
printf implementation now also supports parameter indices in the form of "%[parameterIndex][flags][width][.precision][length]type". A detailed description can be found here.
The NUnit test routines are also modified to work as expected with all Country settings for decimal and group separators.
A bug when specifying a precision with hex format was solved.
- 2007.06.14 -- First release.
- 2009.01.26 -- Update.