C# does allow for indexed access on classes (the [ ] notation), but no built in mechanism to use the same notation on properties. What I mean is, I want to easily do this:

C#:
  1. var testValue = settings.ByName["testSetting"];

or

C#:
  1. settings.ByName["testSetting"] = "testValue";

Usually this is done by creating a class which only reason to exist is to make the property accessible through indexer. For an example of that approach please see the example on MSDN.

I don't like this approach since it forces me to create a class for every property I want use to indexes on, even though they usually are very "thin" classes.

Fortunately lambdas allow for a pretty elegant solution that does not require a subclass for every indexed property. I'll just show the final helper class and go from there:

C#:
  1. public class IndexedProperty<Index, Return> {
  2.   private Func<Index, Return> m_getter;
  3.   private Action<Index, Return> m_setter;
  4.  
  5.   public IndexedProperty(Func<Index, Return> getter, Action<Index, Return> setter) {
  6.     if (getter == null) { throw new ArgumentNullException("getter", "getter is null."); }
  7.     if (setter == null) { throw new ArgumentNullException("setter", "setter is null."); }
  8.  
  9.     m_getter = getter;
  10.     m_setter = setter;
  11.   }
  12.  
  13.   public Return this[Index index]  {
  14.     get { return m_getter(index); }
  15.     set { m_setter(index, value); }
  16.   }
  17. }

One simple example on how to use this class is by implementing the appropriate filtering methods as regular methods hosted within the same class that wants to provide an indexed property. In the example the SettingContainer class wants to allow a direct access to its List of Setting via the name of the setting. It would of course be possible to use Setting GetSetting(string name) but I like the Setting Option[string name] syntax better. But I guess that's just a matter of taste.

In this example the IndexedProperty will be initialized by private methods of the SettingContainer that allow for the appropriate filtering:

C#:
  1. public class SettingContainer {
  2.   public SettingContainer() {
  3.     SettingByName = new IndexedProperty<string, Setting>(GetSettingByName, SetSettingByName);
  4.   }
  5.  
  6.   private Setting GetSettingByName(string name) {
  7.     return m_settingz.Find(
  8.       (s) =>
  9.       {
  10.         return s.Name == name;
  11.       }
  12.     );
  13.   }
  14.  
  15.   private void SetSettingByName(string name, Setting setting)
  16.   {
  17.     var index = m_settingz.FindIndex(
  18.       (u) =>
  19.       {
  20.         return u.Name == name;
  21.       }
  22.     );
  23.     if (index <0) {
  24.       // setting  was not found -> append to list
  25.       m_settingz.Add(setting);
  26.     }
  27.     else {
  28.       m_settingz[index] = setting;
  29.     }
  30.   }
  31.  
  32.   private List<Setting> m_settingz = new List<Setting>();
  33.   public IList<Setting> Settingz {
  34.     get { return m_settingz; }
  35.   }
  36.  
  37.   public IndexedProperty<string, Setting> Option { get; private set; }
  38.  
  39.   public class Setting {
  40.     public string Name { get; set; }
  41.     // ...
  42.   }
  43. }

Of course it is just as valid to use lambdas directly in order to initialize the IndexedProperty. In this next example we will see the use of a similar class - the IndexedGetProperty, which only hosts the possibility to allow get access via the indexed property. Please look at the code in the zip file for the implementation details of IndexedGetProperty.

Please pay special attention to the direct initialization of IndexedGetProperty via lambda.

C#:
  1. public class ReadonlySettingContainer {
  2.   public ReadonlySettingContainer() {
  3.     SettingByName = new IndexedGetProperty<string, Setting>(
  4.       (name) =>
  5.       {
  6.         return m_settingz.Find(
  7.           (s) =>
  8.           {
  9.             return s.Name == name;
  10.           }
  11.           );
  12.       }
  13.       );
  14.   }
  15.  
  16.   private List<Setting> m_settingz = new List<Setting>();
  17.   public IList<Setting> Settingz {
  18.     get { return m_settingz.AsReadOnly(); }
  19.   }
  20.  
  21.   public IndexedGetProperty<string, Setting> SettingByName { get; private set; }
  22.  
  23.   public class Setting {
  24.     public string Name { get; set; }
  25.   }
  26. }

Conclusion:
In this article we have seen a helper class that allows for indexed property without the use of subclasses. Using Func and Action as initialization parameters it helps keeping the SettingContainer class to be short and cohese. The filtering methodology is right were it belongs, within the SettingContainer class itself and not capsuled away in any subclass.

Download Blog.Indexed.zip