#366 – Defining an Indexer with More than One Parameter

An indexer will typically have a single parameter, often based on an integer type.

        public LogMessage this[int i]
        {
            get { return messages[i]; }
            set { messages[i] = value; }
        }

You can also define an indexer that takes more than one parameter.  In the example below, we have an internal data structure that stores a list of messages for each day of the week.  We can retrieve a specific message by passing in a parameter representing the day, as well as a 0-based index into the list of messages for that day.

            // Get 1st Saturday message
            LogMessage msg = log[Days.Sat, 0];

Here’s what the definition of the indexer looks like.

        private Dictionary<Days, List<LogMessage>> dailyMessages = new Dictionary<Days, List<LogMessage>>();

        public LogMessage this[Days day, int i]
        {
            get { return dailyMessages[day][i]; }
        }
Advertisement

#365 – Overloading an Indexer

You can define several versions of an indexer in a class, each indexed using a different type, overloading the indexer.

In the example below, we can index using the Days enumerated type or a 0-based integer representing the day.

    public class Logger
    {
        private Dictionary<Days, LogMessage> dailyMessages = new Dictionary<Days, LogMessage>();

        public LogMessage this[Days day]
        {
            get { return dailyMessages[day]; }
            set { dailyMessages[day] = value; }
        }

        public LogMessage this[int i]
        {
            get { return dailyMessages[(Days)i]; }
            set { dailyMessages[(Days)i] = value; }
        }
    }

Using this class, we could then index using either the Days or the integer type.

            Logger log = new Logger();

            log[Days.Mon] = new LogMessage("Monday was a good day");
            log[2] = new LogMessage("Tuesday not bad either");

In this example, we indexed into the same internal data object.  But we could have also indexed into different objects, depending on the indexer’s type.

#363 – An Indexer Can Have Both get and set Accessors

Similar to a property, an indexer in C# can have either a get accessor for reading an element, a set accessor for writing an element, or both.

  • get accessor – read-only behavior
  • set accessor – write-only behavior
  • get and set accessors – read-write behavior

Here’s an example of a read-write accessor.

    public class Logger
    {
        private List<LogMessage> messages = new List<LogMessage>();

        // Read-write indexer
        public LogMessage this[int i]
        {
            get { return messages[i]; }
            set { messages[i] = value; }
        }

        public void LogAMessage(LogMessage msg)
        {
            messages.Add(msg);
        }
    }

We can then use the indexer to read and write elements in the internal list.

            Logger log = new Logger();

            log.LogAMessage(new LogMessage("This happened", 5));
            log.LogAMessage(new LogMessage("Something else happened", -1));

            LogMessage lm = log[1];   // Get 2nd element
            log[0] = new LogMessage("New 1st guy", 1);  // Replace 1st element

#98 – Using an Indexer to Get a Specific Character In a String

In C#, you can use the indexer operator [ ], to get a specified character in a string.

The indexer takes a zero-based integer as an index into the string.  0 returns the first character and n-1 (where n is the length of the string) returns the last character.

 string s = "ABCDE";
 char c = s[0];   // A
 c = s[2];        // C (3rd char)
 c = s[4];        // E

Using a negative value for the index will result in an IndexOutOfRangeException being thrown.

Note that indexers work to extract Unicode characters only if they are 2-byte UTF16 characters.  The indexer cannot retrieve a 4-byte surrogate pair.

 string s = "A€C";
 char c = s[1];         // Works: €

 s = "A𠈓C";
 c = s[1];       // Doesn't work: unprintable character