The Property Indexer Of Doom

time to read 6 min | 1057 words

C# has indexers, but it doesn’t support indexed properties. By that I mean the ability to do something like:
config.Options[“Username”] = “Ayende”;
To be exact, the above syntax is possible, but only by cheating. While the CLR support indexed properties, in C# you need to return an object which has an indexer, leading to a common pattern of inner classes in order to allow this syntax.
Here is the last such set of classes that you’ll need, served to you by the power of delegates and generics. One thing to note here is that there is some code duplication here, I can't see how to prevent that withou mutiply inheritance, and it's a very small amount of code, so I allowed it.

public class PropertyIndexer<RetType, IndexType>
{
 private Getter getter;
 private Setter setter;
 public delegate RetType Getter(IndexType index);
 public delegate void Setter(IndexType index, RetType value);
 
 public PropertyIndexer(Getter getter, Setter setter)
 {
  this.getter = getter;
  this.setter = setter;
 }
 
 public RetType this[IndexType index]
 {
  get { return getter(index); }
  set { setter(index, value); }
 }
}
public class PropertyIndexerGetter<RetType, IndexType>
{
 private Getter getter;
 
 public PropertyIndexerGetter(Getter getter)
 {
  this.getter = getter;
 }
 
 public RetType this[IndexType index]
 {
  get { return getter(index); }
 }  
}

public class PropertyIndexerSetter<RetType, IndexType>
{
 private Setter setter;
 public PropertyIndexerSetter(Setter setter)
 {
  this.setter = setter;
 }
 
 public RetType this[IndexType index]
 {
  set { setter(index, value); }
 }  
}

The usage of this class is very simple, here is a sample of using it to allow a Linq like querying over a list of objects.

public class UserContainer
{
 private PropertyIndexerSetter<User, string> usersByName;
 private List<User> users;
 
 public EasyIndexedProperties()
 {
  users = DAL.GetUserList();
  usersByName = new PropertyIndexerSetter<stringint>(
   delegate(string userName) 
   { 
    return users.Find(
      delegate(User user) 
       { return user.Name == userName; }
     )
   });
 }
 public IList<User> Users { get { return users; } }
 public User UserByName { get { return usersByName; } }
}

And the client code looks like this:

UserContainer userContainer = new UserContainer(); 
User user = userContainer.UserByname["Ayende"]; 

The constructor is a little bit threatening, but it’s actually easy to understand what is required of you, certainly better than the 1.1 way of inner class that would get it directly, and then iterating over the list explicitly, etc.

Enjoy