Just to ensure we're on the same page, method "hiding" is when a derived class defines a member of the same name as one in the base class (which, if it is a method/property, is not marked virtual/overridable), and when invoked from an instance of the derived class in "derived context", the derived member is used, while if invoked by the same instance in the context of its base class, the base class member is used. This is different from member abstraction/overriding where the base class member expects the derived class to define a replacement, and from scope/visibility modifiers that "hide" a member from consumers outside the desired scope.
The short answer to why it's allowed is that not to do so would force developers to violate several key tenets of object-oriented design.
Here's the longer answer; first, consider the following class structure in an alternate universe where C# doesn't allow member hiding:
public interface IFoo
{
string MyFooString {get;}
int FooMethod();
}
public class Foo:IFoo
{
public string MyFooString {get{return "Foo";}}
public int FooMethod() {//incredibly useful code here};
}
public class Bar:Foo
{
//public new string MyFooString {get{return "Bar";}}
}
We want to uncomment the member in Bar, and by so doing, allow Bar to provide a different MyFooString. However, we cannot do so because it would violate out alternate-reality prohibition on member hiding. This particular example would be rife for bugs and is a prime example of why you might want to ban it; for instance, what console output would you get if you did the following?
Bar myBar = new Bar();
Foo myFoo = myBar;
IFoo myIFoo = myFoo;
Console.WriteLine(myFoo.MyFooString);
Console.WriteLine(myBar.MyFooString);
Console.WriteLine(myIFoo.MyFooString);
Off the top of my head, I'm actually not sure whether you'd get "Foo" or "Bar" on that last line. You'd definitely get "Foo" for the first line and "Bar" for the second, even though all three variables reference exactly the same instance with exactly the same state.
So, the language's designers, in our alternate universe, discourage this obviously bad code by preventing property hiding. Now, you as the coder have a genuine need to do exactly this. How do you get around the limitation? Well, one way is to name Bar's property differently:
public class Bar:Foo
{
public string MyBarString {get{return "Bar";}}
}
Perfectly legal, but it's not the behavior we want. An instance of Bar will always produce "Foo" for the property MyFooString, when we wanted it to produce "Bar". Not only do we have to know that our IFoo is specifically a Bar, we also have to know to use the different accessor.
We could also, quite plausibly, forget the parent-child relationship and implement the interface directly:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
public int FooMethod() {...}
}
For this simple example it's a perfect answer, as long as you only care that Foo and Bar are both IFoos. The usage code a couple examples up would fail to compile because a Bar isn't a Foo and can't be assigned as such. However, if Foo had some useful method "FooMethod" that Bar needed, now you can't inherit that method; you'd either have to clone its code in Bar, or get creative:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
private readonly theFoo = new Foo();
public int FooMethod(){return theFoo.FooMethod();}
}
This is an obvious hack, and while some implementations of O-O language specs amount to little more than this, conceptually it's wrong; if consumers of Bar need to expose Foo's functionality, Bar should be a Foo, not have a Foo.
Obviously, if we controlled Foo, we can make it virtual, then override it. This is the conceptual best practice in our current universe when a member is expected to be overridden, and would hold in any alternate universe that didn't allow hiding:
public class Foo:IFoo
{
public virtual string MyFooString {get{return "Foo";}}
//...
}
public class Bar:Foo
{
public override string MyFooString {get{return "Bar";}}
}
The problem with this is that virtual member access is, under the hood, relatively more expensive to perform, and so you typically only want to do it when you need to. The lack of hiding, however, forces you to be pessimistic about the members which another coder that doesn't control your source code might want to reimplement; the "best practice" for any non-sealed class would be to make everything virtual unless you specifically didn't want it to be. It also still doesn't give you the exact behavior of hiding; the string will always be "Bar" if the instance is a Bar. Sometimes it is genuinely useful to leverage the layers of hidden state data, based on the level of inheritance at which you're working.
In summary, allowing member hiding is the lesser of these evils. Not having it would generally lead to worse atrocities committed against object-oriented principles than allowing it does.