The original principle:
"What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.".
Barbara Liskov, 1987
The word is behavior. The "preconditions and postconditions" understanding is useful for a good design but is not related to LSP.
Let's check this summary of "preconditions and postconditions" theory:
- Don’t implement any stricter validation rules on input parameters than implemented by the parent class.
- Apply at the least the same rules to all output parameters as applied by the parent class.
An indication that it has nothing to do with LSP is: what about VOID methods? VOID does not have OUTPUT parameters. How could this rule be applied to VOID methods? How, according to this rule, could we guarantee to be complying with LSP in VOID methods?
LSP refers to Behavior. When a subclass inherits from a superclass and you have to use some trick to make this work, and the result change the behavior of the program you are breaking LSP.
LSP is about behaviour and the clasic example of Square x Rectangle help us to understand. In fact is the example used by Uncle Bob.
The you inherit Square from Rectangle and overrides SetHeight and SetWidth to force Square act as a Square even if it's a rectangle (by inheritance).
When the user calls SetHeight do not expect Width change.... but will change and this change the expected behavior and break LSP.
This is the problem with Virtuals x LSP