Inserting large amounts data into Excel spreadsheet
public class ReportData
{
    public ReportData(IEnumerable&lt;ReportColumn&gt; columns)
    {
        Columns = columns ?? throw new ArgumentNullException("No valid columns available");
    }

    public IEnumerable&lt;ReportColumn&gt; Columns { get; set; }
}



The Columns property should be get-only.


public string ColumnName { get; set; } 
public object[] ColumnData { get; set; }



Same with these two: it makes no sense to set them in the constructor, even throw an exception if it's null, if you can legally construct the object and then merrily do this:

var data = new ReportData(Enumerable.Empty&lt;ReportColumn&gt;()) { Columns = null };


Note that IEnumerable&lt;T&gt; things should indeed never be null, but any IEnumerable&lt;T&gt; can still be empty without being null. As such, the guard clause is insufficient, since it appears to be there to protect against creating a new ReportData that wouldn't have any columns - Enumerable.Empty&lt;T&gt;() is a perfectly valid input that should be guarded against.

public class ReportData
{
    public reportData(IEnumerable&lt;ReportColumn&gt; columns)
    {
        var reportColumns = columns ?? throw new ArgumentNullException(...);
        if (!reportColumns.Any())
        {
            throw new ArgumentException(...);
        }

        Columns = reportColumns;
    }

    public IEnumerable&lt;ReportColumns&gt; Columns { get; }
}


Now there's no way a ReportData object can have 0 columns, be it during or after construction.

These two could be expression-bodied auto-properties:


private Worksheet Worksheet { get { return Globals.ThisAddIn.Application.ActiveSheet; } }
private Range Cells { get { return Globals.ThisAddIn.Application.ActiveSheet.Cells; } }



Like this:

private Worksheet Worksheet =&gt; Globals.ThisAddIn.Application.ActiveSheet;
private Range Cells =&gt; Globals.ThisAddIn.Application.ActiveSheet.Cells;


This is a serious problem:


// NOTE: Excel uses indexing that starts at 1
if (columnIndex == 0)
    columnIndex = 1;

if (rowIndex == 0)
    rowIndex = 1;



Consider consistently using { } braces to avoid such implicit scopes.

If the columnIndex or rowIndex supplied was 0, then the calling code is off-by-one and this silent one-upping is literally hiding that bug, and there's a very very very high chance that the next call is going to pass a 1 that you're not going to offset, which will produce a result, but very unlikely the expected result.

Consider:


Throwing an ArgumentOutOfRangeException given 0 for column or row index.
Adding XML docs to document that the index parameters are 1-based - OR document them as 0-based, and abstract away the fact that Excel's object model is 1-based by offsetting every provided value, not just 0. In any case, you need XML docs to say this.




My experience with VSTO is that if you don't properly release all the COM objects you ever access, then when your program exits you'll be left with a ghost EXCEL.EXE process in Task Manager, because .NET will be holding on to RCW objects wrapping COM/unmanaged objects that won't be garbage-collected like managed objects would be.

Hence, avoid double-dots like this:

Globals.ThisAddIn.Application.ActiveSheet


That's essentially leaking ThisAddIn, Application, and ActiveSheet objects.

Every single access to Cells is re-dereferencing the same ActiveSheet over and over and over, every time. That makes a lot of redundant member access.

var addin = Globals.ThisAddIn;
var application = addin.Application;
var activeSheet = application.ActiveSheet;

var allCells = activeSheet.Cells;
var cell = allCells[columnIndex][rowIndex];
cell.Value = column.ColumnName;

var cellFont = cell.Font;
cellFont.Bold = true;


...and then each of these objects need to be released... in the reverse order they were accessed:

Marshal.ReleaseCOMObject(cellFont);
Marshal.ReleaseCOMObject(cell);
Marshal.ReleaseCOMObject(allCells);
...


Miss one single COM object, and the EXCEL.EXE process won't be able to shut down correctly.

That makes it a pretty good reason to avoid writing to individual cells, ...regardless of the performance hit ;-)