4
\$\begingroup\$

I've designed a little struct that should help me deal with Excel columns in terms when the letter index is used. The idea is to use the struct implicitly most of the time for iterations over Excel columns etc.

The struct works in the cases i've intended it to. However, on the one hand, I'm not sure if it isn't a little over the top for a struct and i should rather use a class. On the other hand I was a little surprised why the struct did actually work implicitly when combining it with an integer and assigning it to a string value like this

int rowIndex = 1;
ExcelColumn column = 1;
string cellId = column + rowIndex.ToString(); 

results in the string being assigned to

A1

So my questions are:

  • Why is that the case?
  • Is the struct properly designed for its purpose?

Below is the code for the struct:

public struct ExcelColumn
{
    #region Constants
    private const int MAX_COLUMN_COUNT = 16384;
    #endregion

    #region Fields
    private readonly int _value;
    #endregion

    #region Constructors
    public ExcelColumn(int columnIndex)
    {
        if (columnIndex < 1) 
            throw new IndexOutOfRangeException("Index below 1 not allowed.");
        if (columnIndex > MAX_COLUMN_COUNT) 
            throw new OverflowException("Max Column number exceeded");

        _value = columnIndex;
    }
    #endregion

    #region Methods
    private string GetValue()
    {
        string value = "";
        int val = _value - 1;
        do
        {
            value = Convert.ToChar(65 + val % 26) + value;
            val = (val - val % 26) / 26 - 1;
        }
        while (val >= 0);
        return value;
    }
    public override string ToString()
    {
        return GetValue();
    }
    #endregion

    #region Operators
    public static ExcelColumn operator +(ExcelColumn x, ExcelColumn y)
    {
        return new ExcelColumn(x._value + y._value);
    }
    public static ExcelColumn operator +(ExcelColumn x, int y)
    {
        return new ExcelColumn(x._value + y);
    }
    public static ExcelColumn operator +(int x, ExcelColumn y)
    {
        return new ExcelColumn(x + y._value);
    }
    public static ExcelColumn operator ++(ExcelColumn x)
    {
        return new ExcelColumn(x._value + 1);
    }

    public static ExcelColumn operator -(ExcelColumn x, ExcelColumn y)
    {
        return new ExcelColumn(x._value - y._value);
    }
    public static ExcelColumn operator -(ExcelColumn x, int y)
    {
        return new ExcelColumn(x._value - y);
    }
    public static ExcelColumn operator -(int x, ExcelColumn y)
    {
        return new ExcelColumn(x - y._value);
    }
    public static ExcelColumn operator --(ExcelColumn x)
    {
        return new ExcelColumn(x._value - 1);
    }

    public static implicit operator ExcelColumn(int x)
    {
        return new ExcelColumn(x);
    }
    public static explicit operator int(ExcelColumn x)
    {
        return x._value;
    }
    #endregion

    #region Properties
    public string Value { get { return GetValue(); } }
    public int Index { get { return _value; } }
    #endregion
}
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Are you sure that's your test code? As neither int nor ExcelColumn define an implicit conversion to string, I'm finding it hard to believe that this compiles: string cellId = column + rowIndex; . \$\endgroup\$
    – RobH
    Apr 5, 2017 at 6:40
  • \$\begingroup\$ I was a little surprised why the struct did actually work implicitly when combining it with an integer and assigning it to a string value - me either so I tested it and as expected and mentioned by @RobH it didn't work so how could it have worked for you? \$\endgroup\$
    – t3chb0t
    Apr 5, 2017 at 6:47
  • \$\begingroup\$ @t3chb0t - glad I wasn't going mad - I was on my mac which doesn't have a C# compiler on it. I'm not convinced we should close the question because the struct (the main body of the question) is review-able... not sure whether we have a rule about this sort of thing. \$\endgroup\$
    – RobH
    Apr 5, 2017 at 7:32
  • \$\begingroup\$ @t3chb0t Whoops... copied the wrong code... was missing a .ToString(); call. Edited it in the question. \$\endgroup\$
    – Adwaenyth
    Apr 5, 2017 at 7:32

1 Answer 1

2
\$\begingroup\$

Why is that the case?

You need to know what the string + operator does under the covers - it calls String.Concat.

So when you do this:

var someString = aString + anObject;

We get this:

var someString = string.Concat(aString, anObject);

Let's look at the IL for your example:

C#

void Main()
{
    int rowIndex = 1;
    ExcelColumn column = new ExcelColumn(1);
    string cellId = column + rowIndex.ToString(); 
}

IL

IL_0000:  nop         
IL_0001:  ldc.i4.1    
IL_0002:  stloc.0     // rowIndex
IL_0003:  ldloca.s    01 // column
IL_0005:  ldc.i4.1    
IL_0006:  call        UserQuery+ExcelColumn..ctor
IL_000B:  ldloc.1     // column
IL_000C:  box         UserQuery.ExcelColumn
IL_0011:  ldloca.s    00 // rowIndex
IL_0013:  call        System.Int32.ToString
IL_0018:  call        System.String.Concat
IL_001D:  stloc.2     // cellId
IL_001E:  ret        

You can see that your ExcelColumn (column) is being boxed to an object and passed to String.Concat.

So your question is really: why does this work?

string cellId = string.Concat(column, rowIndex.ToString()); 

Answer: because column.ToString() is being called in string.Concat.

Hmmm... I hope that makes sense.


Time to review your code.

Get rid of the regions. They're almost always pointless and you certainly don't need a region around a single constant.

Constants are named PascalCase in C#. MAX_COLUMN_COUNT should be MaxColumnCount.

I think it would be better to store the string representation at creation rather than computing it each time it's needed.

Structs always have a default constructor which means you have a bug.

new ExcelColumn().ToString() // @
\$\endgroup\$
2
  • \$\begingroup\$ But if I do store the string representation, wouldn't default constructor just initialize that string as null which would be equally false? \$\endgroup\$
    – Adwaenyth
    Apr 5, 2017 at 9:35
  • \$\begingroup\$ @Adwaenyth - the string comment is separate to the default constructor comment. I'd recommend making this a class if you want your indexes to be 1-based rather than 0 based. \$\endgroup\$
    – RobH
    Apr 5, 2017 at 9:38

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.