Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A printf implementation in C#

4.41/5 (49 votes)
10 Feb 2015MIT3 min read 5   2.4K  
This article describes an implementation of printf using C#.

Screenshot - printf.jpg

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.

C#
// Ouput: Account balance:       +12.345.678,00 (great)

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:

C#
[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#:

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#:

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.

License

This article, along with any associated source code and files, is licensed under The MIT License