2

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;
}
13
  • Do you mean like if (call is DateTime)? Commented Oct 3, 2017 at 15:42
  • What is your objective after knowing the data type? Commented Oct 3, 2017 at 15:44
  • @DiskJunky Somewhat, but what it would actually look like is something like if (((ExcelCell<object>) cell).Value is DateTime). This just seems to be a bit convoluted, and I'm sure there's a better way. Commented Oct 3, 2017 at 15:44
  • 1
    Declare the interface as IExcelCell<T>. Commented Oct 3, 2017 at 15:45
  • 1
    I don't understand why you're doing this. If you want to create an adapter between your project and Office XML SDK, then your adapter probably shouldn't leak implementation details like Cells. Just have it accept a strongly typed object describing the data to be exported, and then use the sdk to generate the file. Commented Oct 3, 2017 at 15:55

1 Answer 1

3

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
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

I apologize, but I'm not sure how the my GenerateCell method signature would be affected by switching to the factory pattern.
@JDDavis - I didn't explain in the answer how to implement it that way because it is a bit more I think it is best to read about the design pattern first.
However, I am still afraid that the factory pattern wouldn't quite work. I'm not quite setting the formatting on the data, but rather the cell. I may be able to get something to work though.
@JDDavis - true :) It was mainly for the beginning before you updated the rest. Can still work but happy the suggestion above was good for you

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.