TextReader is an abstract base class that represents reading a textual stream. It's like an enumerator for characters (IEnumerable<char>). Common derived classes in the frameworks include StringReader (which presents a string as a text stream) and StreamReader (which presents a text file as a text stream). 
TextReader exposes several read methods including:

A small digression about why TextReader is so cool...
I must digress and mention that I like the TextReader abstraction quite a bit (though arguably it should have been an interface) because:
1) It's a very useful abstraction. Lots of different things can be represented as a character stream (such as strings, files, and keyboard input).
2) It's very easy for derived classes to implement. (contrast to other abstract base classes that have 25+ abstract methods, like XmlReader).
3) It allows for some great class composition:
    Any class that needs a character stream (such as a parser) can just take a TextReader, and now it can easily get input from an endless variety of sources. Another example is that Console.In is also a TextReader, which makes it very easy to override the stdin with a source other than the keyboard. 
    You can also chain TextReaders together. For example, you could build a "EncryptingTextReader" which wraps another TextReader and then does a primitive encoding and forwards the output. Imagine:  EncryptingTextReader.Read() { return Encode(m_innerTextReader.Read()); }

Back on topic: deriving from TextReader:
I'd like to drill into that 2nd point about letting derived classes implement.  In order to derive from an abstract base class, you need to implement all of the abstract methods. Clearly, the more abstract methods, the more difficult it is to derive from.
One way class authors mitigate this is by trying to make methods be virtual (not abstract) and have intelligent default behaviors. For example, there's a lot of redundancy between the TextReader methods above. In fact, all of the methods can be implemented on top of a single character-based Read().  The TextReader class actually provides default implementations of Read(char[], int, int), ReadLine() and ReadToEnd() that build upon Read(). (Unfortunately, it does not provide a good default Peek() implementation).

1) What about Peek()? The default TextReader.Peek() implementation just returns -1, which means end-of-file (use ildasm to check for yourself!). This is very lame and flat out wrong because:
    1a) If somebody overloads Read() but not Peek(), their derived class is now exposing inconsistent behavior for their TextReader. And that could end up being a very hard bug to track down because it's going to manifest in the depths of some 3rd-party parser consuming your derived TextReader. At the very least, the default Peek() should throw a NotImplementedException() to avoid such bugs.
    1b) This is doubly silly because you can build Peek() on top of Read(), so requiring derived classes to overload both Peek() and Read()  means requiring them to do extra work.  The base class could have just provided a default implementation of TextReader.Peek() which uses Read(). Then it would actually behave correctly.

2) Read vs. ReadLine()? Another problem is that derived classes need to specifically override "int Read()" (which reads a single character). Conveniently, the base class then provides default implementations of the other Read functions (Read-character-buffer, ReadLine, ReadToEnd) built on top of this Read-single-char. However, sometimes that's not the best Read() overload for a derived class to implement.
For example, imagine if it's easier to implement the ReadLine() overload instead of Read().  Perhaps your derived TextReader is wrapping some underlying store which gives you an entire line at a time. In that case, it would be nice to have the derived TextReader just have to say:
    public override string ReadLine() { return m_underlyingStore.GetNextLine(); }
and then get the rest of the TextReader functionality for free built on top of that ReadLine().


Sample code for a TextReader based on ReadLine() instead of Read().
Here's a helper class that shows how to deal with both of these problems. It derives from TextReader and then serves as base class for readers that want to just implement ReadLine(). It provides implementations of Read() and Peek() based off the derived implementation of ReadLine(). It uses the TextReader base class default implementations for the other Read overloads.

    // TextReader class requires an implementation of both Read() and Peek().
    // This is a helper class that implements both of those based off a derived implementation of ReadLine().
    // This is useful if a derived TextReader can implement ReadLine() more easily than just Read().   
    public abstract class ReadLineTextReader : TextReader
    {
        // The default TextReader.Peek() implementation just returns -1. How lame!
        // We can build a real implementation on top of Read().
        public override int Peek()
        {
            FillCharCache();
            return m_charCache;
        }

        // Reads one character. TextReader() demands this be implemented.
        public override int Read()       
        {
            FillCharCache();
            int ch = m_charCache;
            ClearCharCache();
            return ch;
        }

#region Character cache support
        int m_charCache = -2; // -2 means the cache is empty. -1 means eof.
        void ClearCharCache()
        {
            m_charCache = -2;
        }
        void FillCharCache()
        {           
            if (m_charCache != -2) return; // cache is already full
            m_charCache = GetNextCharWorker();
        }
#endregion

#region Worker to get next signle character from a ReadLine()-based source
        // The whole point of this helper class is that the derived class is going to
        // implement ReadLine() instead of Read(). So mark that we don't want to use TextReader's
        // default implementation of ReadLine(). Null return means eof.
        public abstract override string ReadLine();

        // Gets the next char and advances the cursor.
        int GetNextCharWorker()
        {
            // Return the current character
            if (m_line == null)
            {
                m_line = ReadLine(); // virtual
                m_idx = 0;
                if (m_line == null)
                {
                    return -1; // eof
                }
                m_line += "\r\n"; // need to readd the newline that ReadLine() stripped
            }
            char c = m_line[m_idx];
            m_idx++;
            if (m_idx >= m_line.Length)
            {
                m_line = null; // tell us next time around to get a new line.
            }
            return c;
        }

        // Current buffer
        int m_idx = int.MaxValue;
        string m_line;
#endregion
    }

So you can now derive from ReadLineTextReader, just implement the ReadLine() method and get a 100% fully functioning TextReader.
One downside of this base class is that it does introduce 3 additional fields (int, int, string), so I'm not advocating the BCL replace TextReader with this implementation. However you may find it useful for your own work. (I found it very useful for a pet project; stay tuned for future posts...)