1

I have an interface 'IBase' that specifies a nullable int. A later interface 'IDerived' hides the nullable int and 'redefines' it as non-nullable.

interface IBase
{
    int? Redefineable { get; set; }
}

interface IDerived : IBase
{
    new int Redefineable { get; set; }
}

The class that implements these interfaces must explicitly implement the hidden property, however it's private so the client can't see it.

class TheClass : IDerived
{
    public int Redefineable { get; set; }
    int? IBase.Redefineable { get; set; }
}

However, even though it's a private property, I can still access it through the IBase interface!

var o = new TheClass();
o.Redefineable = 1; // ok

var hack = o as IBase;
hack.Redefineable = null; // uh!

This seems like some kind of violation of C# access modifiers, but either way it isn't really what I had in mind for redefining (not just hiding) a property. It's correct in the sense that it does what you're asking, get an IBase interface which has a nullable int but this is non-intuitive to the client who could then modify the wrong version of the property.

What I really want, is that if the client accesses IBase.Redefinable, then it behaves as if it's accessing the IDerived.Redefinable property, the 'real' property of TheClass. That way it's actually redefined, as in back through the hierarchy.

class TheClass : IDerived
{
    public int Redefineable { get; set; }
    int? IBase.Redefineable { 
        get { 
            // redirect to redefined property
            return this.Redefineable; 
        }
        set
        {
            // stop client setting it to null
            if (!value.HasValue)
                throw new InvalidOperationException();

            // redirect to redefined property
            this.Redefineable = value.Value;
        }
    }
}

This just feels like a hack, almost as if I'm missing something, so I want to ask if anyone knows a better/alternative way to implement re-definable properties?

MarkB
  • 174
  • 14
  • 1
    I think you need to brush up on how polymorphism works. If you have an `IBase` and you're using its property, it's going to be how `IBase` defines it. If you have an `IDerived` and you're using its property, it's going to be how `IDerived` defines it. – rory.ap Jan 26 '15 at 17:15
  • That is just how it is suppose to work. – leppie Jan 26 '15 at 17:15
  • 1
    Explicit interface implementations are not `private` in the usual sense. For that reason it is not allowed to specify the `private` modifier for them. – Jeppe Stig Nielsen Jan 26 '15 at 17:15
  • 1
    Hiding a member (here a property) by using the `new` modifier is ***not*** "redefining" it. It is simply introducing another member which looks the same. It is very rarely a good thing to do. The original member is not removed, and it can always be used, by upcasting to the type (here base interface) which defines it. – Jeppe Stig Nielsen Jan 26 '15 at 17:18
  • An explicit interface implementation is only there to solve the ambiguity problem, caused by having to implement two members with the same name. It is not an access restriction. – Hans Passant Jan 26 '15 at 17:25
  • Your sample is pretty standard way of dealing with several variants of the same property/method... Usually it is shown as [implementation of generic IEnumerable](http://stackoverflow.com/questions/11296810/how-do-i-implement-ienumerablet), but property works as well... So not sure what exactly you are calling "hack". – Alexei Levenkov Jan 26 '15 at 17:25

1 Answers1

4

However, even though it's a private property, I can still access it through the IBase interface!

It's not a private property. It's just a property using explicit interface implementation. That means it's public through the interface, but only available through the interface. Explicit interface implementation is mostly designed to make it feasible to implement "contradictory" interfaces, as well as being used to "discourage" (but not prohibit) the use of some interface methods. It's not meant to give the impression that the members don't exist at all.

Fundamentally, it sounds like you shouldn't be using inheritance here - if you don't want something to be able to act as an IBase, you shouldn't inherit from IBase.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • ok thanks for the information on the private/public issue. I do want it to act as an IBase - in all other cases except for accessing that one property, obviously this is simplified example of the problem, in the real code there are some 30 interfaces in a hierarchy which implement multiple inheritance, the equivalent to IBase is somewhere in the middle, it has other properties too. I would say it **is** an IBase - but with a **redefined** property. I don't know a better way to implement it, hence my question. – MarkB Jan 26 '15 at 18:19
  • @MarkB: But an interface which is just like IBase but with a different property type simply isn't IBase. Perhaps you should have that property in its own interface, so you could have two alternative interfaces depending on whether it should have type `int?` or `int`... – Jon Skeet Jan 26 '15 at 19:25
  • Of course, but all I'm effectively doing is adding a rule to the implementation of that interface, so although it's defined as nullable, trying to set the value to null (on certain implementing classes) will result in an exception. Is it any different to having an int that throws an exception if you set it to the number 5? – MarkB Jan 26 '15 at 20:42
  • @MarkB: Yes - you're trying to change the type of it. If you just want to implement it in a particular way, that's fine (so long as the interface is documented that implementations may put particular requirements on the value). If you're happy enough with that - and if no `IBase` clients will *expect* to set a value of null - then the implementation you've got seems reasonable. – Jon Skeet Jan 26 '15 at 20:46
  • Microsoft have an implementation of the same system, they simply don't include a nullable 'version' in the hierarchy. This breaks the spec (UML 2.x), I'm not sure if it breaks compliance. I had a look at your two alternative interface idea, but it makes the hierachy too complex along with the supporting client code, and misaligns the interfaces with the spec but it's one I'll consider for the future. Thanks. – MarkB Jan 26 '15 at 22:08