Regarding LSP
That doesn't break the LSP.
The LSP states that if Derived
is a subtype of Base
, then any code depending on Base
(e.g, a method with a Base
parameter, like void DoSomething(Base b)
) can be replaced with an instance of Derived
, without any surprising effects.
And as you pointed out, if you assign an instance of Derived
to a Base
variable, the Base
implementation will be called.
That's the expected behaviour, since Say
is not virtual. This means that code written against a Base
variable, expects the Base
implementation to be called.
Practical purpose
You can think of new
methods as a way to circumvent a non-overridable method - with a caveat! You'll have to program against that specific type - not its interface.