JD Davis
JD Davis

Reputation: 3720

Passing around generic objects using an interface?

I'm building a tool to simply the exporting to excel for later re-use. One of the issues I'm running into is an easy, and reliable way of passing around an object that contains my cell data.

The class that contains my cell data looks like:

public interface IExcelCell { 

}

public class ExcelCell<T> : IExcelCell
{
    public T Value { get; set; }
}

And I'm using it like so:

private void GenerateCell(IExcelCell cell, int currentColumn)
{
    // check type of Value, apply optional formatting
}

Essentially what I need to do is check if the incoming data is of a certain data-type, e.g. string, double, DateTime. Is there a convenient way of doing this?

Edit

This is the relevant exportation code to illustrate somewhat of what I'm trying to do:

public class ExcelExporter
{
    private ExcelPackage excelPackage;
    private OfficeOpenXml.ExcelWorksheet currentWorksheet;
    private int currentRow;

    public ExcelPackage ExportExcelWorkbook(ExcelWorkbook workbook)
    {
        excelPackage = new ExcelPackage();
        GenerateWorkbook(workbook);

        return excelPackage;
    }

    private void GenerateWorkbook(ExcelWorkbook workbook)
    {
        foreach (var worksheet in workbook.Worksheets)
        {
            GenerateWorksheet(worksheet);
        }
    }

    private void GenerateWorksheet(ExcelWorksheet worksheet)
    {
        currentWorksheet = excelPackage.Workbook.Worksheets.Add(worksheet.Name);
        currentRow = 1;

        foreach (var section in worksheet.Sections)
        {
            GenerateSection(section);
        }

    }

    private void GenerateSection(ExcelSection section)
    {
        if (!section.Name.IsNullOrEmpty())
        {
            currentWorksheet.Cells[currentRow, 1].Value = section.Name;
            currentRow++;
        }

        if (section.Headers != null && section.Headers.Any())
        {
            GenerateHeaders(section.Headers);
            currentRow++;
        }

        foreach (var row in section.Rows)
        {
            GenerateRow(row);
            currentRow++;
        }
    }

    private void GenerateHeaders(IEnumerable<string> headers)
    {
        var enumerable = headers as IList<string> ?? headers.ToList();

        for (var i = 0; i < enumerable.Count(); i++)
        {
            currentWorksheet.Cells[currentRow, i + 1].Value = enumerable[i];
        }
    }

    private void GenerateRow(ExcelRow row)
    {
        var currentColumn = 0;

        foreach (var cell in row.RowData)
        {
            GenerateCell(cell, currentColumn);
            currentColumn++;
        }
    }

    private void GenerateCell(IExcelCell cell, int currentColumn)
    {
        var excelCell = (ExcelCell<object>) cell;

        if (excelCell.Value is DateTime)
        {
            currentWorksheet.Cells[currentRow, currentColumn].Style.Numberformat.Format = "mm/dd/yyyy";
        }

        currentWorksheet.Cells[currentRow, currentColumn].Value = excelCell.Value;
    }
}

Edit 2

Taking Gilad's advice from his answer, I came up with a slightly different solution:

// interface
public interface IExcelCell { 
    object Value { get; set; }
    string NumberFormat { get; }
}

// implementation
public class ExcelCell : IExcelCell
{
    public object Value { get; set; }

    public string NumberFormat => null;
}

public class DateTimeExcelCell : IExcelCell
{
    public object Value { get; set; }

    public string NumberFormat => "mm/dd/yyyy";
}

// Cell generation
private void GenerateCell(IExcelCell cell, int currentColumn)
{
    if (cell.NumberFormat != null)
    {
        currentWorksheet.Cells[currentRow, currentColumn].Style.Numberformat.Format = cell.NumberFormat;
    }

    currentWorksheet.Cells[currentRow, currentColumn].Value = cell.Value;
}

Upvotes: 2

Views: 62

Answers (1)

Gilad Green
Gilad Green

Reputation: 37281

Seeing updated question:

As I suggested bellow use the is operator on cell:

if (cell is ExcelCell<DateTime>)
{
    currentWorksheet.Cells[currentRow, currentColumn].Style.Numberformat.Format = "mm/dd/yyyy";
}

But still see answer below about the Factory pattern part


Original answer:

I guess cleanest will be to declare the interface as generic and have the Value in it. If not then you can use the is operator. For example:

IExcelCell cell = new ExcelCell<DateTime>();
bool result1 = cell is ExcelCell<DateTime>; // true
bool result2 = cell is ExcelCell<int>; // false

So in your function:

private void GenerateCell(IExcelCell cell, int currentColumn)
{
    if(cell is ExcelCell<DateTime>)
    {
    }
}

Or the as operator if you want to use it:

private void GenerateCell(IExcelCell cell, int currentColumn)
{
    var c = cell as ExcelCell<DateTime>
    if(c != null)
    {
        // TODO - use casted c
    }
}

From your comments it seems like the purpose of knowing the type is to perform formatting. I suggest that instead have a Factory pattern that given a specific type formats that specific type. Or if you change the interface then something like:

public interface IExcelCell<T> 
{
    T Value { get; set; }
}

public class DateTimeExcelCell : IExcelCell<DateTime>
{
    public DateTime Value { get; set; }

    public override string ToString()
    {
        // TODO - formatting you want for DateTime
    }
}

Upvotes: 3

Related Questions