Reputation: 63
Jon Skeet brought up this issue once in his videos (though didn't provide with an answer).
Let's say we have a Class named Person and the Person class has Name property
Then we have another class, Spy. Of course a Spy is a Person so we will derive from the Person class.
public class Person
{
public string Name { get; set; }
}
public class Spy : Person
{
}
We don't want people to know the Spy's name so we'd want this to give a compilation error:
static void ReportSpy(Spy spy) {
string name = spy.Name;
}
or either:
static void ReportSpy(Spy spy)
{
Person spyAsPerson = spy;
string name = spyAsPerson.Name;
}
How could we prevent this kind of things from happening?
Upvotes: 6
Views: 248
Reputation: 6531
If part of being a person is disclosing your name: Spy's aren't people
Having Spy inherit from person breaks the Liskov substitution principle: An object may be replaced with its subtype.
If Spys don't disclose their name they shouldn't be People in the context of your design. Perhaps you could design it differently:
public interface IPerson
{
void EatLunch();
}
public interface INameDisclosingPerson : IPerson
{
string Name {get; set; }
}
public interface ISpy : IPerson
{
void DrinkCocktail();
Package MakeDrop();
}
An example of this poor design in the real world is NetworkStream. It implements the Position
property by throwing a NotSupportedException. So code you write for a Stream
may break at runtime for the NetworkStream
. I'm also not a fan of this. A piece of design guidance: wrong things should break at compilation time and objects that inherit from interfaces they can't implement are terrible.
Upvotes: 6
Reputation: 10068
Make the Name
property virtual
in the base Person
class. In derived Spy
class, override the property and throw Exception
in getter.
public class Person
{
public virtual string Name { get; set; }
}
public class Spy : Person
{
public override string Name
{
get
{
throw new Exception("You never ask for a spy's name!");
}
set
{
base.Name = value;
}
}
}
But, rather than throwing exception, I'd suggest something like
get
{
return "**********";
}
Because, it breaks LSP (mentioned in another answer). What that means here (just an example) is, I can always do like
Person x = new Spy();
and pass it to some other method, which might be like
void RegisterForNextBallGame(Person p)
{
playerList.Add(p.Name);
}
This method being unaware of the some spy roaming around the stadium, crashes while doing a simple honest duty!
Edit
Just to make it clear, this name=**********
is still not a right solution. It will just save from the exceptions! Later, one might find lot of Persons walking down the code with name ********** which will cause later surprises and other issues.
A better solution would be a better design. Check Nathan's answer to get some hint.
Upvotes: 6
Reputation: 14896
You can hide base class method or property with new
keyword:
public class Person
{
public string Name { get; set; }
}
public class Spy : Person
{
public new string Name
{
get { throw new InvalidOperationException(); }
}
}
It will not give you compilation errors, and you will still be able to access the base class Name
property if you cast.
If you can modify the base class, change the property to virtual. Then you can override it in the derived and class and throw exception even with polymorphic calls.
All this will work at runtime, nothing you can do about it at compile time.
And as others mentioned in their answers, such design breaks Liskov substitution principle and should be avoided.
Upvotes: 1
Reputation: 1150
You can't. As already mentioned you could throw an Exception when accessing Spy's Name property but this would still compile. And, also already mentioned, this would break Liskov substitution principle and, I'd like to add, the open-closed principle as well.
Upvotes: 1