Were either 
InnerList or 
List properties of CollectionBase virtual, it would be pretty straightforward. Unfortunatelly, this is not the case. Nevertheless it is still doable. Essentially, you have to write a typed IList-wrapper, which delegates all calls to a wrapped list. Here's how I do that (please note explicit interface implementations):
Code:
namespace CommonServer.ObjectModel.Business.Collections
{
   public abstract class ListWrapperBase : IList
   {
      #region Private Member Variables
      private IList wrappedList;
      #endregion
      #region Protected Properties
      protected IList WrappedList
      {
         get { return wrappedList; }
      }
      #endregion
      public ListWrapperBase(IList wrappedList)
      {
         this.wrappedList = wrappedList;
      }
      #region IList Members
      public bool IsReadOnly
      {
         get { return wrappedList.IsReadOnly; }
      }
      public bool IsFixedSize
      {
         get { return wrappedList.IsFixedSize; }
      }
      int IList.Add(object value)
      {
         return wrappedList.Add(value);
      }
      bool IList.Contains(object value)
      {
         return wrappedList.Contains(value);
      }
      public void Clear()
      {
         wrappedList.Clear();
      }
      int IList.IndexOf(object value)
      {
         return wrappedList.IndexOf(value);
      }
      void IList.Insert(int index, object value)
      {
         wrappedList.Insert(index, value);
      }
      void IList.Remove(object value)
      {
         wrappedList.Remove(value);
      }
      public void RemoveAt(int index)
      {
         wrappedList.RemoveAt(index);
      }
      object IList.this[int index]
      {
         get { return wrappedList[index]; }
         set { wrappedList[index] = value; }
      }
      public int Count
      {
         get { return wrappedList.Count; }
      }
      public object SyncRoot
      {
         get { return wrappedList.SyncRoot; }
      }
      public bool IsSynchronized
      {
         get { return wrappedList.IsSynchronized; }
      }
      public void CopyTo(Array array, int index)
      {
         wrappedList.CopyTo(array, index);
      }
      public IEnumerator GetEnumerator()
      {
         return wrappedList.GetEnumerator();
      }
      #endregion
   }
}
And here's a typed ancestor:
Code:
using System.Collections;
using CommonServer.ObjectModel.Business.Objects;
namespace CommonServer.ObjectModel.Business.Collections
{
   public class CityListWrapper : ListWrapperBase
   {
      public City this[int index]
      {
         get { return WrappedList[index] as City; }
         set { WrappedList[index] = value; }
      }
      public CityListWrapper(IList wrappedList) :
         base(wrappedList)
      {
      }
      public int Add(City businessObject)
      {
         return WrappedList.Add(businessObject);
      }
      public bool Contains(City businessObject)
      {
         return WrappedList.Contains(businessObject);
      }
      public int IndexOf(City businessObject)
      {
         return WrappedList.IndexOf(businessObject);
      }
      public void Insert(int index, City businessObject)
      {
         WrappedList.Insert(index, businessObject);
      }
      public void Remove(City businessObject)
      {
         WrappedList.Remove(businessObject);
      }
   }
}
And finally business object, which contains a collection of City objects (Country in my case)
Code:
public class Country
{
        private IList cities = new ArrayList();
        // Yes, creating a new instance of CityListWrapper is performance penalty
        public CityListWrapper Cities
        { get { return new CityListWrapper(cities); } } 
}