24

I have two classes, none of which I can change in any way:

Class 1: Takes a TextWriter as constructor parameter and uses it as an output stream.

Class 2: Provides a method WriteLine(string).

I need an adapter, such that all the output of Class1 is written to Class2. Therefore I started an adapter which extends TextWriter and buffers incoming text, flushing it to the class2 instance as soon as a new line arrives.

However, there are many and more methods in TextWriter - which should I implement? Output in Class1 is string only.

According to MSDN one should override Write(char) as a minimum, however, this enforces me to do all the \r\n new line handling myself as well...

Q1: Do you know of a better way to reach my goal? Q2: If no, which TextWriter methods should I override to have minimum implementation effort.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
D.R.
  • 20,268
  • 21
  • 102
  • 205

2 Answers2

28

Implementing Write(char) on your TextWriter derived class is all you need to do. If somebody calls WriteLine on your new class, the base class WriteLine method is called. It will do the right thing: call your Write method with the individual \r and \n characters.

Actually, WriteLine(string) looks something like this:

void WriteLine(string s)
{
    Write(s);
    Write("\r\n");
}

And Write(string) is, in effect:

foreach (char c in s)
{
    Write(c);
}

All of the Write methods in TextWriter resolve to something that calls Write(char) in a loop.

You really don't have to implement anything else. Just override Write(char) and plug it in. It will work.

You can override those other methods. Doing so will make your class a little more efficient (faster). But it's not required. I say do the simplest thing you can. Then, if you determine after profiling that your custom writer is too slow, override other methods as necessary.

Here's a minimal TextWriter descendant:

public class ConsoleTextWriter: TextWriter
{
    public override void Write(char value)
    {
        Console.Write(value);
    }

    public override Encoding Encoding
    {
        get { return Encoding.Default; }
    }
}

If I then write:

using (var myWriter = new ConsoleTextWriter())
{
    myWriter.Write("hello, world");
    myWriter.WriteLine();
    myWriter.WriteLine();
    myWriter.WriteLine("Goodbye cruel world.");
    myWriter.Write("Fee fie foe foo!");
}

The output is:

hello, world

Goodbye cruel world.
Fee fie foe foo!
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • 1
    Thank you for your answer, however, when do i know when it's time to flush it via WriteLine() to the underlying class 2? I can only do this at "full lines". E.g. I would have to lookout for NewLines in Write(char)? Is \n a NewLine? \r\n? Maybe on a different platform something else? – D.R. Jul 18 '13 at 10:01
  • @D.R.: Yes. Your `Write` method will have to look for newlines. On Windows, a newline is typically "\r\n". Look at the [Environment.Newline](http://msdn.microsoft.com/en-us/library/system.environment.newline.aspx) property. – Jim Mischel Jul 18 '13 at 12:32
  • Yup, thank you. I wrote an EndsWith(string) extension method for StringBuilder to look if it ends with Environment.Newline, if the case, I flush it to the underlying class 2 and clear the StringBuilder. – D.R. Jul 18 '13 at 12:35
  • 1
    "You can override those other methods. Doing so will make your class a little more efficient (faster)" Which methods do you mean? Only overriding `Write(char)` results in very slow printing. – Protector one Jun 15 '16 at 10:58
  • 2
    @Protectorone: Overriding just about any of the virtual methods will increase performance. You're right that the minimal implementation--overriding only `Write(char)`--results in very slow printing. But it *works*, which is a very handy thing. – Jim Mischel Jun 15 '16 at 11:21
  • I just discovered that in .Net 4.0 it is not enough to override character writing, you also have to override WriteLine(String) because it does not use the Write(Char) method. I did not check any other framework versions. – bikeman868 Jun 08 '18 at 14:45
  • 1
    @bikeman868: `WriteLine(string)` fills a character buffer and calls `Write(Char[], int buffer, int count)`, which in turn calls `Write(Char)` in a loop. See https://referencesource.microsoft.com/#mscorlib/system/io/textwriter.cs,452. I'm pretty sure it's always been that way, but it's possible I'm mistaken. – Jim Mischel Jun 08 '18 at 14:58
  • 1
    Hit F12 in Visual Studio and look at the decompiled source code. WriteLine very clearly does not use Write(Char) in the current version of .Net 4.0. – bikeman868 Jun 09 '18 at 15:44
  • This answer was based on an incorrect understanding of the question. The OP wants text to flow from TextWriter to that other class that implements WriteLine, not the other way around. – Andriy Volkov Jun 26 '18 at 14:31
  • @zvolkov On what do you base that comment? The OP's "Q2" asked which `TextWriter` methods he needed to override, which is the question this answer addresses. And, considering that he accepted the answer, I can only assume that it did indeed solve his problem. – Jim Mischel Jun 26 '18 at 15:14
  • OP said "I need an adapter, such that all the output of Class1 is written to Class2" and then in a comment he said "when do i know when it's time to flush it via WriteLine() to the underlying class 2". So his underlying output stream only accepts whole strings, while TextWriter accepts individual characters - and his question was how to connect the two. This answer fails to address that key aspect of the problem. Your comment "Your Write method will have to look for newlines" is what OP was looking for - it would be good if an accepted answer had an example of that, instead of what it has now. – Andriy Volkov Jun 26 '18 at 15:50
3

I'm adding another answer because I couldn't get the above answer to work!

In my experience, I must override WriteLine() and WriteLine(string) in order for those functions to work.

Here's an example that can be used to write a web page as a long-running task goes on. BTW, the HttpResponse object is from ASP.net MVC.

public class WebConsoleWriter : TextWriter
{
    HttpResponseBase Response { get; set; }
    bool FlushAfterEveryWrite { get; set; }
    bool AutoScrollToBottom { get; set; }
    Color BackgroundColor { get; set; }
    Color TextColor { get; set; }

    public WebConsoleWriter(HttpResponseBase response, bool flushAfterEveryWrite, bool autoScrollToBottom)
    {
        Response = response;
        FlushAfterEveryWrite = flushAfterEveryWrite;
        AutoScrollToBottom = autoScrollToBottom;
        BackgroundColor = Color.White;
        TextColor = Color.Black;
    }

    public WebConsoleWriter(HttpResponseBase response, bool flushAfterEveryWrite,  bool autoScrollToBottom, Color backgroundColor, Color textColor)
    {
        Response = response;
        FlushAfterEveryWrite = flushAfterEveryWrite;
        AutoScrollToBottom = autoScrollToBottom;
        BackgroundColor = backgroundColor;
        TextColor = textColor;
    }

    public virtual void WritePageBeforeStreamingText()
    {
        string headerFormat =
@"<!DOCTYPE html>
<html>
<head>
    <title>Web Console</title>
    <style>
        html {{
            background-color: {0};
            color: {1};
        }}
    </style>        
</head>
<body><pre>";
        string backgroundColorHex = ColorTranslator.ToHtml(BackgroundColor);
        string foregroundColorHex = ColorTranslator.ToHtml(TextColor);
        Response.Write(string.Format(headerFormat, backgroundColorHex, foregroundColorHex));

        // Add a 256 byte comment because I read that some browsers will automatically buffer the first 256 bytes.
        Response.Write("<!--");
        Response.Write(new string('*', 256));
        Response.Write("-->");
        Response.Flush();
    }

    public virtual void WritePageAfterStreamingText()
    {
        Response.Write("</pre></body></html>");
    }

    public override void Write(string value)
    {
        string encoded = Encode(value);
        Response.Write(encoded);            
        if (FlushAfterEveryWrite)
            Response.Flush();
    }

    public override void WriteLine(string value)
    {
        Response.Write(Encode(value) + "\n");
        if (AutoScrollToBottom)
            ScrollToBottom();
        if (FlushAfterEveryWrite)
            Response.Flush();
    }

    public override void WriteLine()
    {
        Response.Write('\n');
        if (AutoScrollToBottom)
            ScrollToBottom();
        if (FlushAfterEveryWrite)
            Response.Flush();
    }

    private string Encode(string s)
    {
        return s.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;");
    }

    public override void Flush()
    {
        Response.Flush();
    }

    public void ScrollToBottom()
    {
        Response.Write("<script>window.scrollTo(0, document.body.scrollHeight);</script>");
    }

    public override System.Text.Encoding Encoding
    {
        get { throw new NotImplementedException(); }
    }
}
Vivian River
  • 31,198
  • 62
  • 198
  • 313