Multiple-inheritance, composition and single responsibility principle in .NET

.NET is often chided by C++ developers for failing to support multiple-inheritance. The reply is often Favor object composition over class inheritance – a mantra chanted from everywhere including the opening chapters of the Gang of Four’s Design Patterns book.

If the accepted mantra is that your object should expose interfaces and delegate the implementation of those interfaces elsewhere then it could really do with some better support than .NET currently offers especially where the interface comprises more than a member or two.

Consider the following fragment of a class for customer price-lists (properties and methods omitted). We decide to support IList so that consumers of our class can add, remove and iterate over the prices in a familiar manner (principle of least surprise).

public class CustomerPriceList : IList<ProductPrice> {
    private List<ProductPrice> productPrices = new List<ProductPrice>();
    public Customer Customer;
}

Implement interface

Visual Studio offers some assistance where you can choose Implement interface IList which gives you all the method definitions with the very unhelpful body of throwing an exception of “This method or operation is not implemented”. It requires some work to fill in all these definitions to something that works:

public class CustomerPriceList : IList<ProductPrice> {
    private  List<ProductPrice> productPrices = new  List<ProductPrice>();
    public  Customer Customer;

    public int IndexOf(ProductPrice item) {
        return productPrices.IndexOf(item);
    }

    public void Insert(int index, ProductPrice item) {
        productPrices.Insert(index, item);
    }

    public void RemoveAt(int index) {
        productPrices.RemoveAt(index);
    }

    public ProductPrice this[int index] {
        get { return productPrices[index]; }
        set { productPrices[index] = value; }
    }

    public void Add(ProductPrice item) {
        productPrices.Add(item);
    }

    public void Clear() {
        productPrices.Clear();
    }

    public bool Contains(ProductPrice item) {
        return productPrices.Contains(item);
    }

    public void CopyTo(ProductPrice[] array, int arrayIndex) {
        productPrices.CopyTo(array, arrayIndex);
    }

    public int Count { get { return productPrices.Count; } }

    public bool IsReadOnly { get { return ((IList)productPrices).IsReadOnly; } }

    public bool Remove(ProductPrice item) {
        return productPrices.Remove(item);
    }

    public IEnumerator<ProductPrice> GetEnumerator() {
        return productPrices.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return ((IEnumerable) productPrices).GetEnumerator();
    }
}

This is a lot of effort for a cluttered solution.

Use inheritance to subclass List

public class CustomerPriceList : List<ProductPrice> {
  public Customer Customer;
}

Small amount of code but not an option if you have a class hierarchy in place or need to implement multiple interfaces.

Expose IList property directly

public class CustomerPriceList : IListable<ProductPrice> {
  private List<ProductPrice> productPrices = new List<ProductPrice>();
  public Customer Customer;
  public IList<ProductPrice> ProductPrices { get { return productPrices; } }
}

This works but means CustomerPriceList can not control any of the IList implementation such as validation.

Methods may also start accepting IList instead of CustomerPriceList because developers imagine the parts to be more decoupled than they actually are and are encouraged to code to interfaces not concrete classes.

Refactoring away from this at a later date would require a IList wrapper than delegated calls back to the containing class to prevent an interface-breaking change.

Introduce interface to declare IList available

Add an interface that signifies a IList can be obtained by calling the named method, e.g.

public interface IListable<T> {
  IList<T> GetList();
}

This is a similar pattern to that of IEnumerable and IEnumerator whereby one interface signifies the availability of the other. In this example our class would look like:

public class CustomerPriceList : IListable<ProductPrice> {
  private List<ProductPrice> productPrices = new List<ProductPrice>();
  public Customer Customer;
  public IList<ProductPrice> GetList() {
    return productPrices;
  }
}

Which is less code, a closer adherence to single responsibility principle (SRP) and the ability to change without breaking the interface although it still does nothing to prevent passing IList or IListable interfaces where CustomerPriceList would be more suitable. An IPriceList class could be introduced although it starts to feel like abstract infinity.

Improved support from .NET

I’d really like to see .NET improve on the support for interfaces and composition, like perhaps the following:

public class CustomerPriceList : IList<ProductPrice> goto productPrice {
  private IList<ProductPrice> productPrice = new IList<ProductPrice>();
  public Customer Customer;
}

This would signify to the compiler that all IList interfaces should be wired up to the productPrice variable unless explicitly defined and gives goto a whole new lease of life ;-)

[)amien

5 responses

  1. Avatar for Rik Hemsley

    You could call a static method on another class from your ctor. This method would add simple versions of the IList methods where they don't already exist.

    This breaks IntelliSense and might destabilise the universe.

    I can't think of a better way to do it (in a not-exactly-dynamic language). Have you seen an implementation elsewhere?

    Rik Hemsley 6 August 2007
  2. Avatar for steve

    Hey, and you've found a use for the 'goto' keyword again. That in itself has to be a positive thing ;)

    I think like all mantras the 'compose over inherit' principle is correct in moderation. There are plenty of scenarios where composition leads to harder to maintain code, simply because in just about all languages it has to be manually wired up. This is of course its power as well, and I'm not sure you can get the full benefit of the technique without incurring this. At the very least I think you'd need to be able to direct the delegation via a factory method for the contained object (lazy creation) to avoid hard-coding the composed type.

    Many modern C++ techniques also echew inheritence in favour of templated composition through Policy Based Design. Again this works very well for some things, particularly low-level utility classes as opposed to higher-evel library APIs. Each technique has its place.

    steve 6 August 2007
  3. Avatar for John Chapman

    I'm failing to see the big accomplishments of this post. A big issue to me is the fact that when you don't implement the interface you lose your ability to validate and monitor the actions taking place with the collection. If someone calls Add() your class doesn't have any hooks to validate that added reference. I think in this case actually implementing the interface on your class is relatively minor yet offers a lot of added benefit, such as being able to reject invalid assignments.

    Plus, I was confused by something else. How is using a read only property any different from using a get method to return the collection? I fail to see how one approach is superior to the other. They both compile down to the exact same thing. The property becomes a get_ method and the method stays as a method of your name. How is this any different? I hope you don't mean the names are different so it doesn't have to be a list of product prices. If that is the concern change the property to List, and you have the same result. Is there something I'm missing?

    John Chapman 11 December 2007
  4. Avatar for Damien Guard

    What I'm proposing with this post is compiler magic that would signal that an interface a class claims to support should be automatically wired up to a private instance variable where no specific implementation of a method or property exists in the class.

    Having a get method isn't any better than a read only property, I was just closely following what the IEnumerable / IEnumerator pattern is to see what the result would look like.

    I think we are after the same thing. We want to be able to define a class, say it supports interface X and that we will be forwarding all methods and properties for that interface to our own private method except any we specifically write - in our case Add.

    Other alternatives to the specific Add on collection problem you mention would be to force the collection property to be of a listening type we can hook into or to internally hold the concrete collection but expose it out through a proxy object on one of our properties that calls us back using internal methods.

    Damien Guard 11 December 2007
  5. Avatar for Stealf

    This is a bit of a late reply but I had essentially the same idea as this and agree that it would be a great addition to the language.

    https://social.msdn.microsoft.com/Forums/en/CSharplanguage/thread/5120d5ef-97ec-4de6-ab6c-9a7b5dfc50de

    Stealf 13 October 2009