Menu

Finished code

For the impatient, head over to my Github repository for a working example along the class code: link.

The rest of the post will walk through how to create table printer for console in C#.

Starting with class structure

When it comes to creating class, writing out its structure helps to understand what we are trying to achieve and thus makes programming both time and energy efficient.

Therefore, let’s create a class named “Table”, whose constructor accepts columns as collection list. Along that, the class will also have some public methods such as AddRow, overridden ToString, and Print. The latter will print table as string to console using Console.WriteLine.

Here is the class structure code:

class Table {
    public Table(params string[] columns) {

    }

    public void AddRow(params object[] values) {

    }

    public override string ToString() {

    }

    public void Print() {
        Console.WriteLine(ToString());
    }
}

Class constructor and AddRow method

Since the table needs both columns and rows, we need to store these values somewhere. For that let’s create two private collection variables with getters and setters:

public class Table {
    private List<object> _columns { get; set; }
    private List<object[]> _rows { get; set; }

Now it is time to hook given data with class’ private collection variables. For that, let’s make constructor pass columns argument to the private _columns list variable, along initialize _rows default value to List object. Also, check for incoming argument to avoid null value or empty list.

public Table(params string[] columns) {
    if (columns == null || columns.Length == 0) {
        throw new System.ArgumentException("Parameter cannot be null nor empty", "columns");
    }

    _columns = new List<object>(columns);
    _rows = new List<object[]>();
}

Let’s do same with AddRow method. In this case, check for incoming row’s length because if it does not match with columns count, throw error.

public void AddRow(params object[] values) {
    if (values == null) {
        throw new System.ArgumentException("Parameter cannot be null", "values");
    }

    if (values.Length != _columns.Count) {
        throw new Exception("The number of values in row does not match columns count.");
    }

    _rows.Add(values);
}  

Where magic happens – ToString method

Now we have made to the most important part – ToString method, which returns table in string format as following image shows:

The first thing that appears in the table is divider with character “-” that gets repeat by horizontal length of table. The second line has columns with section dividers, and each column has width which equals its rows’ maximum string length. We will use private method for that but about that a little bit later. From third line will appear formatted rows and the very last line has once again divider. For creating the table string, we will use StringBuilder which makes manipulating string very simple.

As mentioned before, we need to format columns with its rows’ maximum string length. For that let’s create private method called “GetColumnsMaximumStringLengths” that will return list which represents each column.

Here is the method’s full code:

private List<int> GetColumnsMaximumStringLengths() {
     List<int> columnsLength = new List<int>();

     for (int i = 0; i < _columns.Count; i++) {
         List<object> columnRow = new List<object>();
         int max = 0;

         columnRow.Add(_columns[i]);

         for (int j = 0; j < _rows.Count; j++) {
             columnRow.Add(_rows[j][i]);
         }

         for (int n = 0; n < columnRow.Count; n++) {
             int len = columnRow[n].ToString().Length;

             if (len > max) {
                 max = len;
             }
         }

         columnsLength.Add(max);
     }

     return columnsLength;
}

To understand how this method works, let’s say that there are two columns called I”, and Name and two rows with values 1 with Greg, and 2 with Martin.

What the method does it loops through each column and at each loop cycle, it will create columnRow list with current column value.

After that, loop through rows and add to “columnRow” value that is equal to current column index of row in loop. For instance, if current loop index is on Name column (i = 1), pick only Greg and Martin.

The last step is looping through columnRow values and finding the maximum string length of values. The maximum value of each loop will be added to returning list.

While this approach might not be the best, it does serve given functionality. However here is a challenge: try to implement same method using LINQ only.

String formatting

Coming back to table string, we can now use columnsLength variable to create row’s string representation:

public override string ToString() {
  StringBuilder tableString = new StringBuilder();
  List<int> columnsLength = GetColumnsMaximumStringLengths();

  var rowStringFormat = Enumerable
      .Range(0, _columns.Count)
      .Select(i => " | {" + i + ",-" + columnsLength[i] + "}")
      .Aggregate((total, nextValue) => total + nextValue) + " |";

  // Ignore return value error currently.
}

This code, which uses LINQ, is quite self-explaining: create enumerable range of column list length, loop through range with columns maximum length, and sum it all together.

With that we can create both column and row strings (add after rowStringFormat variable):

string columnHeaders = string.Format(rowStringFormat, _columns.ToArray());
List<string> results = _rows.Select(row => string.Format(rowStringFormat, row)).ToList();

Divider

The only thing missing is divider lines. For that we need to find table’s maximum length as following:

int maximumRowLength = Math.Max(0, _rows.Any() ? _rows.Max(row => string.Format(rowStringFormat, row).Length) : 0);
int maximumLineLength = Math.Max(maximumRowLength, columnHeaders.Length);

With them we can finally create divider string:

string dividerLine = string.Join("", Enumerable.Repeat("-", maximumLineLength - 1));

// For older NET version use "string.Format" instead of "$".
string divider = $" {dividerLine} ";

Returning value

The final part is appending dividers, column headers and rows to tableString value.

tableString.AppendLine(divider);
tableString.AppendLine(columnHeaders);

foreach (var row in results) {
    tableString.AppendLine(divider);
    tableString.AppendLine(row);
}

tableString.AppendLine(divider);

Add the returning value of tableString that gets converted to string:

return tableString.ToString();

To recap, here is the method’s full code:

public override string ToString() {
    StringBuilder tableString = new StringBuilder();
    List<int> columnsLength = GetColumnsMaximumStringLengths();

    var rowStringFormat = Enumerable
        .Range(0, _columns.Count)
        .Select(i => " | {" + i + ",-" + columnsLength[i] + "}")
        .Aggregate((total, nextValue) => total + nextValue) + " |";

    string columnHeaders = string.Format(rowStringFormat, _columns.ToArray());
    List<string> results = _rows.Select(row => string.Format(rowStringFormat, row)).ToList();

    int maximumRowLength = Math.Max(0, _rows.Any() ? _rows.Max(row => string.Format(rowStringFormat, row).Length) : 0);
    int maximumLineLength = Math.Max(maximumRowLength, columnHeaders.Length);

    string dividerLine = string.Join("", Enumerable.Repeat("-", maximumLineLength - 1));
    string divider = $" {dividerLine} ";

    tableString.AppendLine(divider);
    tableString.AppendLine(columnHeaders);

    foreach (var row in results) {
      tableString.AppendLine(divider);
      tableString.AppendLine(row);
    }

    tableString.AppendLine(divider);

    return tableString.ToString();
}

Testing it out

To see, if this actually work, create a console application and make Main method generate a table with 40 rows with random values:

static void Main(string[] args) {
    Table tbl = new Table("user id", "password", "age");
    Random rnd = new Random();

    for (int i = 0; i < 40; i++) {
      // Generate string with length of 16.
      var guid = Guid.NewGuid().ToString().Substring(0, 16);

      // Generate random number.
      var age = rnd.Next(0, int.MaxValue);

      tbl.AddRow(i, guid, age);
    }

    tbl.Print();

    Console.ReadKey();
}

This should be result:

Console output

Hope you enjoyed this and feel free to write your thoughts into comments.