Tuesday, November 9, 2010

Neat Way to Write a Read-Only Extendable List

The other day one of my coworkers wanted to to do the following:

  • Have a base class that has a property that returns a list
  • The list should be immutable
  • The list will contain a list of items to exclude elsewhere in the code
  • Sub-classes should be able to add items to the list but not remove them

One way to implement this would be something like the following:


public class Base
{
    private  IList<string> _excludeList = (new List<string>(){ "a", "b", "c" }).AsReadOnly();
    public virtual IList<string> ExcludeList
    {
        get { return _excludeList; }
    }
}


public class Subclass : Base
{


    public override IList<string> ExcludeList
    {
        get
        {
            List<string> excludeList = new List<string>(base.ExcludeList);
            excludeList.Add("d");
            return excludeList.AsReadOnly();
        }
    }
}


My inclination was that this might be inefficient because of the  repeated creation of the list.  Let's examine a couple pieces of this code and see if we can clean it up.  The first is we use a list because the framing of the question lead us there.  We just want to be able to iterate over the list, how we accomplish that is unimportant, therefore List is not the type we want.  There really is no reason that this needs to be something other than an IEnumerable.  If we return an IEnumerable rather than a list we can use the yield keyword to do some fancy rewriting.  We also are no longer creating new collections every time we call the subclass version:



public class Base
{
     private IList<string> _excludeList = (new List<string>() { "a", "b", "c" }).AsReadOnly();
     public virtual IEnumerable<string> ExcludeList
     {
         get { return _excludeList; }
     }
}


public class Subclass : Base
{


    public override IEnumerable<string> ExcludeList
    {
        get
        {
            foreach (string s in base.ExcludeList)
            {
                yield return s;
            }


            yield return "d";
        }
    }
}


We now are avoiding creation of new lists and the way it works is fairly clean.  The unfortunate part of this is that its not very efficient in the large case.  In talking about this I originally thought this would be a more efficient way of doing meeting our requirements because there would be fewer lists being created.  My quick performance testing showed quite the opposite.  In the small cases the difference in performance is negligible; I could not get a consistent result of one being quicker than the other.  In the case where I make the initial list "a" - "z" and the added items "aa" - "mm" the performance difference was significant.  The rewritten version took about twice as long on average.  I did not test a small initial list and a long additional list nor did I test a long initial list and a short additional list.  The test case I ran proved to me that my theory about it being more efficient was wrong so I saw no reason to continue.


Since the performance of this method turns out to be poor this remains nothing more than an interesting way to use the yield operator.



1 comment: