I forget where I got the OrderBy/OutToFile code from, the ObjectDumper is from the MS sample. The pretty formatter mods are mine. 

//typical usage
ObjectDumper.WritePretty(stuff.OrderBy(sort));

//write objectdumper output to file (remove the using statement to write to console)
using (new OutToFile("stuffpretty.txt"))
{

//example: sort and some modification of the output based on what date is in the column
string sort="t"; //[-]p[ropertyName] [DESC] 
var fcb = new ObjectDumper.Callbacks();
fcb.fColumn = (string h) => { return h == "Time" ? "" : h; };
fcb.fwDate = (DateTime dt) => { return dt > DateTime.Today.AddDays(-1) ? "<< " + dt.ToShortDateString() : "   " + dt.ToShortDateString(); };
// pretty format stuff object and sort by a column specd in sort string (from user input)
ObjectDumper.WritePretty(stuff.OrderBy(sort), fcb);

}

 

Class (remember to add namespace to it, then import that namespace so you get the OrderBy query extension):

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Reflection;
using System.Linq.Expressions;


    public static class QueryExtensions
    {

        private static Dictionary<TKey, TValue> DictionaryFromKeys<TKey, TValue>(IEnumerable<TKey> keysource, TValue defvalue)
        {
            var myDict = new Dictionary<TKey, TValue>();
            foreach (TKey c in keysource) { if (!myDict.ContainsKey(c)) myDict.Add(c, defvalue); }
            return myDict;
        }


        //[-]p[ropertyName] [DESC]
        //public static IQueryable<T> SortBy<T>(this IQueryable<T> source, string propertyName)
        public static IQueryable<T> OrderBy<T>(this IEnumerable<T> iesource, string propertyName) //returns iesource back if property was invalid!!! (no fail)
        {
            if (iesource == null) throw new ArgumentNullException("source");
            var source = iesource.AsQueryable<T>(); //++
            // DataSource control passes the sort parameter with a direction if the direction is descending           
            int descIndex = propertyName.IndexOf(" DESC");
            if (descIndex >= 0) propertyName = propertyName.Substring(0, descIndex).Trim();
            if (String.IsNullOrEmpty(propertyName)) return source;
            if (propertyName[0] == '-') { propertyName = propertyName.Substring(1); descIndex=0; }  //*** [-]
            if (propertyName == "-" || propertyName == "+") return source; // existing ordering not known? / not supported
            if (String.IsNullOrEmpty(propertyName)) return source;

            var flds = typeof(T).GetFields(); var prps = typeof(T).GetProperties();
            MemberInfo foundprop = prps.FirstOrDefault(x => x.Name.ToLowerInvariant().StartsWith(propertyName.ToLowerInvariant()));
            MemberInfo foundfld = flds.FirstOrDefault(x => x.Name.ToLowerInvariant().StartsWith(propertyName.ToLowerInvariant()));
            if (foundprop == null && foundfld == null) return source; //foundprop = propertyName; // *** RETURN unmodified if property was invalid!!! (no fail)
           
            ParameterExpression parameter = Expression.Parameter(source.ElementType, String.Empty);
            MemberExpression property=null;
            if (foundprop != null) property = Expression.Property(parameter, foundprop.Name);
            else if (foundfld != null) property = Expression.Field(parameter, foundfld.Name);
            LambdaExpression lambda = Expression.Lambda(property, parameter);

            string methodName = (descIndex < 0) ? "OrderBy" : "OrderByDescending";

            Expression methodCallExpression = Expression.Call(typeof(Queryable), methodName,
                                                new Type[] { source.ElementType, property.Type },
                                                source.Expression, Expression.Quote(lambda));

            return source.Provider.CreateQuery<T>(methodCallExpression);
        }

        //NameValueCollection allows key string to have multiple string values. This extension method allows NVC usage in LINQ
        public static IEnumerable<KeyValuePair<string, string>> ToPairs(this System.Collections.Specialized.NameValueCollection collection)
        {
            if (collection == null) throw new ArgumentNullException("collection");
            return collection.Cast<string>().Select(key => new KeyValuePair<string, string>(key, collection[key]));
        } 

    }

    // If you want CSV output there's http://www.codeproject.com/KB/linq/LINQtoCSV.aspx
    // transferring data to Excel http://support.microsoft.com/kb/306023
    /// <summary>
    ///using (new OutToFile("dump.txt")) // overwrites
    ///{
    ///    ObjectDumper.WritePretty(...);
    ///    ObjectDumper.WritePretty(...);
    ///}
    /// </summary>
    public class OutToFile : IDisposable
    {
        private StreamWriter fileOutput;
        private TextWriter oldOutput;

        /// <summary>
        /// Redirect the console output to specified file
        /// </summary>
        public OutToFile(string outFileName) : this(new FileStream(outFileName, FileMode.Create)) { }
        /// <summary>
        /// Redirect the console output to specified Stream
        /// </summary>
        public OutToFile(Stream writableStream)
        {
            oldOutput = Console.Out;
            fileOutput = new StreamWriter(writableStream);
            fileOutput.AutoFlush = true;
            Console.SetOut(fileOutput);
        }
        /// <summary>
        /// Redirect the console output to Stream.Null
        /// </summary>
        public OutToFile() : this(Stream.Null) { }
        // Dispose() is called automatically when the object 
        // goes out of scope
        public void Dispose()
        {
            Console.SetOut(oldOutput);  // Restore the console output
            fileOutput.Close();        // Done with the file
        }
    }



    public class ObjectDumper
    {
        public static bool OdOutputNullableNulls = false; // write xxx? fieldName=null;'s in the output

        public static void Write(object o) { Write(o, 0, true); }
        public static void Write(object o, bool autoTabSize) { Write(o, 0, autoTabSize); }
        public static void Write(object o, int depth, bool autoTabSize)
        {
            ObjectDumper dumper = new ObjectDumper(depth, autoTabSize);
            dumper.WriteObject(null, o);
        }

        public static List<string> WritePretty(object o) { return WritePretty(o, 0, null); }
        public static List<string> WritePretty(object o, Callbacks OutputFormatter) { return WritePretty(o, 0, OutputFormatter); }
        public static List<string> WritePretty(object o, int depth, Callbacks OutputFormatter)
        {

            ObjectDumper dumper = new ObjectDumper(depth, true);
            if (OutputFormatter!=null) dumper.formatter = OutputFormatter;
            if (o is IEnumerable)
            {
                dumper.writer = new StreamWriter(System.IO.Stream.Null);
                var cache = new List<object>();
                dumper.cachePass = true;
                foreach (var co in (IEnumerable)o) cache.Add(co);
                dumper.WriteObject(null, cache);
                dumper.writer.Close();
                dumper.cacheCount = cache.Count;
                dumper.cachePass = false;
                dumper.writer = Console.Out;
                dumper.WriteObject(null, cache);
            }
            else dumper.WriteObject(null, o);
            return dumper.fields;
        }

        bool cachePass = false;
        int cacheCount = 0;
        public class Callbacks
        {
            public Func<string, string, string> fValue = (a, b) => { return b; };
            public Func<string, string> fColumn = (a) => { return a; };
            public Func<string, string> fwString = (a) => { return a; };
            public Func<DateTime, string> fwDate = (a) => { return a.ToShortDateString(); };
            public Func<string, object, object> fWriteValue = (a, b) => { return b; };
            public Action<int> fPrintNext = (a) => { };
            

        }
        //   field  value  ret:formattedvalue
        Callbacks formatter = new Callbacks();

        internal TextWriter writer;
        int pos;
        int level;
        int depth;

        private ObjectDumper(int depth, bool autoTabSize)
        {
            this.autoTabSize = autoTabSize;
            this.writer = Console.Out;
            this.depth = depth;
        }

        private void Write(string s)
        {
            if (s != null)
            {
                writer.Write(s);
                pos += s.Length;
            }
        }

        private void WriteIndent()
        {
            for (int i = 0; i < level; i++) writer.Write("  ");
        }

        private void WriteLine()
        {
            writer.WriteLine();
            pos = 0;
            curval = 0; //***
        }

        private void WriteTab()
        { //*** added ability to grow the tab size
            if (!autoTabSize) { Write("  "); while (pos % 8 != 0) Write(" "); }
            else
            {
                Write(" "); while (pos % 4 != 0) Write(" ");
                if (!vallengths.ContainsKey(curval)) vallengths.Add(curval, 0);
                if (vallengths[curval] < pos) vallengths[curval] = pos;
                else
                {
                    int ofs = vallengths[curval] - pos;
                    for (int i = 0; i < ofs; i++) Write(" ");
                }
            }
            curval++;
        }

        //***
        Dictionary<int, int> vallengths = new Dictionary<int, int>(20);
        List<string> fields = new List<string>(20);
        int curval = 0;
        bool autoTabSize;

        private void WriteObject(string prefix, object o)
        {
            if (o == null || o is ValueType || o is string)
            {
                WriteIndent();
                Write(prefix);
                WriteValue(o);
                WriteLine();
            }
            else if (o is IEnumerable)
            {
                foreach (object element in (IEnumerable)o)
                {
                    if (element is IEnumerable && !(element is string))
                    {
                        WriteIndent();
                        Write(prefix);
                        Write("...");
                        WriteLine();
                        if (level < depth)
                        {
                            level++;
                            WriteObject(prefix, element);
                            level--;
                        }
                    }
                    else
                    {
                        WriteObject(prefix, element);
                    }
                }
            }
            else
            {
                MemberInfo[] members = o.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance); // BindingFlags.DeclaredOnly | BindingFlags.FlattenHierarchy
                WriteIndent();
                Write(prefix);
                bool propWritten = false;
                foreach (MemberInfo m in members)
                {
                    //if (m.Name.Contains("NNN")) continue;
                    FieldInfo f = m as FieldInfo;
                    PropertyInfo p = m as PropertyInfo;
                    if (f != null || p != null)
                    {
                        if (propWritten)
                        {
                            WriteTab();
                        }
                        else
                        {
                            propWritten = true;
                        }
                        if (!fields.Contains(m.Name)) fields.Add(m.Name); ///***

                        var colstr = formatter.fColumn(m.Name);

                        Type t = f != null ? f.FieldType : p.PropertyType;
                        object vlue=null;
                        if (t.IsValueType || t == typeof(string))
                            vlue = f != null ? f.GetValue(o) : p.GetValue(o, null);


                        bool writeIfNull = true; // !(!ODwriteNULLs && vlue == null); //++
                        if (vlue == null && Nullable.GetUnderlyingType(t) != null) writeIfNull = OdOutputNullableNulls;

                        if (writeIfNull && !string.IsNullOrEmpty(colstr))
                        {
                            Write(colstr);
                            Write("=");
                        }
                        if (writeIfNull && vlue != null)
                        {
                            WriteValue(formatter.fWriteValue(m.Name, vlue));
                        }
                        else
                        {
                            if (typeof(IEnumerable).IsAssignableFrom(t))
                            {
                                Write("...");
                            }
                            else
                            {
                                object x = f != null ? f.GetValue(o) : p.GetValue(o, null);
                                if (x == null || x.ToString() == x.GetType().ToString()) { if (writeIfNull) Write("{ }"); }//+ x==null ||
                                else Write("{" + formatter.fValue(m.Name, x.ToString()) + "}"); // *** display the object.ToString() if overriden
                            }
                        }
                    }
                }
                if (propWritten) { WriteLine(); }
                if (level < depth)
                {
                    foreach (MemberInfo m in members)
                    {
                        FieldInfo f = m as FieldInfo;
                        PropertyInfo p = m as PropertyInfo;
                        if (f != null || p != null)
                        {
                            Type t = f != null ? f.FieldType : p.PropertyType;
                            if (!(t.IsValueType || t == typeof(string)))
                            {
                                object value = f != null ? f.GetValue(o) : p.GetValue(o, null);
                                if (value != null)
                                {
                                    level++;
                                    WriteObject(formatter.fColumn(m.Name) + ": ", formatter.fWriteValue(m.Name,value));
                                    level--;
                                }
                            }
                        }
                    }
                }
            }

            if (!cachePass)
            {
                if (prnti < cacheCount)
                {
                    formatter.fPrintNext(prnti);
                    prnti++;
                }
            }
        }
        int prnti = 0;

        private void WriteValue(object o)
        {
            if (o == null)
            {
                Write("null");
            }
            else if (o is DateTime)
            {
                Write(formatter.fwDate((DateTime)o));
                //Write(((DateTime)o).ToShortDateString());
            }
            else if (o is ValueType || o is string)
            {
                Write(formatter.fwString(o.ToString()));
            }
            else if (o is IEnumerable)
            {
                Write("...");
            }
            else
            {
                Write("{ }");
            }
        }

        // NOT TESTED
        static IEnumerable<string> GetFields(object o, int level, int depth)
        {
            List<string> fields = new List<string>();
            if (o == null || o is ValueType || o is string)
            {
            }
            else if (o is IEnumerable)
            {
                foreach (object element in (IEnumerable)o)
                {
                    if (element is IEnumerable && !(element is string))
                    {
                        if (level < depth)
                        {
                            level++;
                            GetFields(element, level, depth);
                            level--;
                        }
                    }
                    else
                    {
                        GetFields(element, level, depth);
                    }
                }
            }
            else
            {
                MemberInfo[] members = o.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
                foreach (MemberInfo m in members)
                {
                    FieldInfo f = m as FieldInfo;
                    PropertyInfo p = m as PropertyInfo;
                    if (f != null || p != null)
                    {
                        if (!fields.Contains(m.Name)) fields.Add(m.Name); ///***
                    }
                }
            }
            return fields;
        }
    }