I think what you're encountering is not so much a Liskov Substitution Principle violation as you are encountering a polymorphism limitation in most languages.
With something like List<CalendarBaseItem>
the compiler is inferring that you're only dealing with CalendarBaseItem
which obviously can't be true if CalendarBaseItem
is abstract--but that's what a strongly-typed language does: It's only been told about CalendarBaseItem
so that's what it limits usage to.
There are patterns that allow you to deal with this sort of limitation. The most popular is the double-dispatch pattern: a specialization of multiple dispatch that dispatches method calls to the run-time type. This can be accomplished by providing an override, that when dispatched, dispatches the intended method. (i.e. "double dispatch"). It's hard to associate exactly to your circumstances because of the lack of detail. But, if you wanted to do some processing based on some sort of other type for example:
public abstract class CalendarBaseItem
{
abstract void Process(SomeData somedata);
//...
}
public class RotaItem : CalendarBaseItem
{
public override void Process(SomeData somedata)
{
// now we know we're dealing with a `RotaItem` instance,
// and the specialized ProcessItem can be called
someData.ProcessItem(this);
}
//...
}
public class SomeData
{
public void ProcessItem(RotaItem item)
{
//...
}
public void ProcessItem(NoteItem item)
{
//...
}
}
which would replace something like:
var someData = new SomeData();
foreach(var item in calendarItems)
someData.ProcessItem(item);
Now, that's the "classical" way of doing in in C#--which spans all versions of C#. With C# 4 the dynamic
keyword was introduced to allow run-time type evaluation. So, you could do what you want without having to write the double-dispatch yourself simply by casting your item to dynamic
. Which forces the method evaluation to occur at run-time and thus will chose the specialized override:
var someData = new SomeData();
foreach(var item in calendarItems)
someData.ProcessItem((dynamic)item);
This introduces potential run-time exceptions that you'd likely want to catch and deal with--which is why some people don't like this so much. It's also currently very slow in comparison, so it's not recommended in tight loops that are performance sensitive.