Introduction
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#:
namespace FormatHelper
{
void FormatHelper::FormatDouble(String ^%sResult, String ^sFormat, double fVal)
{
CString sStr;
sStr.Format (CString(sFormat), fVal);
sResult = gcnew String(sStr);
}
}
Call it like this from C#:
using FormatHelper;
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.
Update
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.
History
- 2007.06.14 -- First release.
- 2009.01.26 -- Update.