Looks to be valid and reasonable ask in context of example you quoted. Going back to basic definition of run time ploymorphism is "same interface but different implementation"
So in your example : Animal basically provides you an interface whoAmI(). But you will get behavior of actual type that variable Animal points to (this is called run time dispatch or dynamic dispatch) depending upon concrete implementation of that type:
ref2.whoAmI(); /* will say I am Dog */
ref3.whoAmI(); /* will say I am snake*/
What is the point of this behavior : Its one of the most important concept of OOP which facilitate encapsulation for example.
For example : You might have one method which does a lot of thing. It will display animal name, animal picture and history. Lets say that method looks something like :
public void ShowAnimalInfo(Animal animal)
{
animal.WhoAmI();
var picture = animal.GetImage();
var history = animal.GetHistory();
/* statement and logic to process picture and history on some UI */
}
Now the consumer of this method can use this method like :
ShowAnimalInfo(new Dog()); /*OR*/
ShowAnimalInfo(new Cow());
If you notice, your method ShowAnimalInfo() is abstracted from actual animal and can be re used by just working on interface provided by Animal and let run time take care of invoking actual implementation.