6

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?

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
JDoe
  • 63
  • 5
  • Possible duplicate of [How can I hide a base class public property in the derived class](http://stackoverflow.com/questions/1443886/how-can-i-hide-a-base-class-public-property-in-the-derived-class) – Luc Morin Oct 10 '15 at 19:08
  • 3
    If a spy refuses to give their name, it would lead to suspicion that they are a spy, It would be better if the spy gave a false name, like "John Doe". – user1620220 Oct 10 '15 at 19:11
  • 1
    Remove this : _we'd want this to give a compilation error_ . Otherwise , the accepted answer doesn't answer the question. – Royi Namir Oct 10 '15 at 19:28
  • @user1620220 yeah, or that. IRL spies do have names. But may be that the real analogue to this spy is something like the NetworkStream example, where Position is not a thing that can be implemented. – Nathan Cooper Oct 10 '15 at 19:28

4 Answers4

6

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.

Arghya C
  • 9,805
  • 2
  • 47
  • 66
  • 1
    Nice, but this will throw a run time error, not a compilation error. – Zohar Peled Oct 10 '15 at 19:06
  • Yes, you are right. But, this goes closest to what op can easily and successfully implement in a C# code. Simply removing a property from a class hierarchy (giving compilation error) is not supported in .Net AFAIK. Please correct me if I'm wrong :) – Arghya C Oct 10 '15 at 19:29
  • 1
    Your "**********" getter is something I'd find very surprising for a get-set string property. I'm not a big fan of surprises in general. The exception you're looking for is a `NotSupportedException`, but if you read the IL carefully it turns out that these are a subclass `MyDesignIsTerribleExceptions` and should be avoided. I believe the answer to the question is proper design, rather than this 'how-do-I-shoot-myself-in-the-foot-at-runtime' stuff. – Nathan Cooper Oct 10 '15 at 19:35
  • @NathanCooper I agree with your point completely, and I do not say this is the *right* way of doing it. I have edited my answer to make that more clear. But at the same time, it's hard to explain a correct design in a SO thread! When you write `ISpy : IPerson`, it makes sense for someone who already understands it. For others, it'd be like "Why I'd implement an `IPerson` in my `Spy` just to eat lunch!" – Arghya C Oct 10 '15 at 19:49
6

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.

Nathan Cooper
  • 6,262
  • 4
  • 36
  • 75
1

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.

Jakub Lortz
  • 14,616
  • 3
  • 25
  • 39
  • 1
    So if I have a method called `IntegrateForName` that takes a `Spy`, they'll never crack. But if i pass the `Spy` to the `AsknicelyForName` method that takes a `Person` they'll tell me everything? I suppose it just goes to show that torture never works. – Nathan Cooper Oct 10 '15 at 19:40
  • Nice example. Well, you can't do much more with the current design. Next step is realizing that in this context, Spy is not a Person (like you wrote in your answer). – Jakub Lortz Oct 10 '15 at 19:50
1

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.

Silas
  • 1,140
  • 11
  • 12