Examining the first approach in which CustomerArrayList is Inherited from Systems.Collections.ArrayList I feel I must be missing something.
The base class ArrayList for example exposes it's Add method as:
public virtual System.Int32 Add ( System.Object value )
By adding the suggested:
public int Add(Customer customer)
you are actually overloading rather than overriding the method because the signature of the method is different.
That is to say my CustomerArrayList can be called with a Customer as an argument (as we want), but I have done nothing to hide the base functionality, so I can still add any old object (derived from system.object) to the CustomerArrayList as well (which I don't want).
So in terms of a "strongly typed" ArrayList the first approach is really no improvement.
You could perhaps overide Add(Object obj) and perform some type checking there to make sure obj was of type customer, but this is a bit untidy. Methods 2 and 3 remain far superiour, but as Amit points out are quite typing intensive and have problems of their own.