As Nicholas Carey's answer points out, Console.WriteLine merely write to the Console.Out TextWriter Stream.
Knowing this information, we can rewrite the original method as follows:
public static void GetInstalledApps(TextWriter writer)
{
string registry_key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryKey key = Registry.LocalMachine.OpenSubKey(registry_key);
{
foreach (string subkey_name in key.GetSubKeyNames())
{
RegistryKey subkey = key.OpenSubKey(subkey_name);
{
// While this won't throw an Exception when GetValue returns null,
// as WriteLine will "output nothing" for null values,
// this logic might be bugged: the next example has a possible solution.
// No more hard-coding of Console.WriteLine, instead we use
// writer.WriteLine, where writer is the TextWriter that
// has been supplied as an argument.
writer.WriteLine(subkey.GetValue("DisplayName"));
}
}
}
}
Then the code can be called in the following fashion to obtain the original semantics of writing to the console.
GetInstalledApps(Console.Out);
Now, say we want to write to a different stream, we just have to specify it. From the link provided in a comment, we can see how easy it is to create a StreamWriter for a file. StreamWriter extends TextWriter can can thus be passed to our method. As such, it is easy to write to a file:
using (StreamWriter writer = new StreamWriter("theFile.txt"))
{
GetInstalledApps(writer);
}
However, I would suggest moving the writing out of the method. That is, the method should do only one thing: it should only fetch the installed applications.
Thus, consider the following code which uses yield
to create an implicit iterator. However, building/returning a List programatically or using LINQ to generate the Enumerable result would also have worked just fine. I've also modified the function such that it will return a value, even when no DisplayName is set.
public static IEnumerable<string> GetInstalledApps()
{
string registry_key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryKey key = Registry.LocalMachine.OpenSubKey(registry_key);
{
foreach (string subkey_name in key.GetSubKeyNames())
{
RegistryKey subkey = key.OpenSubKey(subkey_name);
{
// I've also added fix for what I believe that was a "silent bug"
// that allowed entries without a display name to be "lost".
// This code returns the subkey name if there is no display name.
var value = subkey.GetValue("DisplayName") as string;
if (!string.IsNullOrEmpty(value)) {
yield return value;
} else {
// No display name, but there is still an entry!!
yield return subkey_name;
}
}
}
}
}
Then we can move the usage of the data outside the function which fetches the data - this reduces coupling and makes the code more flexible. Perhaps later we want to use the code to update a ListBox instead of writing to a file.
using (StreamWriter writer = new StreamWriter("theFile.txt"))
{
foreach (var app in GetInstalledApps()) {
writer.WriteLine(app);
}
}
If GetInstalledApps
is written in the above manner where it separates out the IO concern, then can also be used with File.*AllText
after simply transforming IEnumerable<string>
into an appropriate string, such as with String.Join
. However, I would still use a loop and WriteLine as per above unless I needed to return a string object.