What is wrong with hiding a property in an interface so that I can change its declaration to return a derived type of the original property?
I'm sure this must have been asked before, but I can't find it, and apologies for the long question.
Say I have this situation:
public interface A
{
B TheB{get;}
}
public interface MoreSpecificA : A
{
MoreSpecificB TheMoreSpecificB{get;}
}
public interface B{...}
public interface MoreSpecificB:B{...}
I would like users of MoreSpecificA
to be able to get at the B which is a MoreSpecificB
. They could do this by calling TheB
and cast it, or they could call the method TheMoreSpecificB
. I could also declare MoreSpecificA
like so:
public interface MoreSpecificA : A
{
new MoreSpecificB TheB{get;}
}
so that now they can just use the same method and get back a MoreSpecificB
.
Using the new
to hide a method puts my teeth on edge, so why is this a bad idea? It seems like a reasonable thing to do here.
The general suggestion in most cases I have seen for this seems to be to use generics instead, but this seems to have a problem in that if I have a MoreSpecificA
and I want to return it in a method that declares the return type as A
then I have to have MoreSpecificA
extend A
which gives ambiguity when accessing TheB
on the MoreSpecificA
instance as it doesn't know if you want A.TheB
or MoreSpecificA.TheB
public interface ABase<T> where T : B
{
T TheB{get;}
}
public interface A : ABase<B>
{
}
public interface MoreSpecificA : ABase<MoreSpecificB>,A
{
}
public class blah
{
public A GetA(MoreSpecificA specificA)
{
return specificA; //can't do this unless MoreSpecificA extends A
}
public B GetB(MoreSpecificA specificA)
{
return specificA.TheB; //compiler complains about ambiguity here, if MoreSpcificA extends A
}
}
which could be solved by declaring a new TheB on MoreSpecificA (but the new issue again).
If MoreSpecificA doesn't extend A then the first method in the class blah
above complains as now as MoreSpcificA can't be converted to A.
Whilst writing this I have noticed that if I declare my BaseA to be contravariant like this:
public interface ABase<out T> where T : B
{
T TheB{get;}
}
and my class to be
public class blah
{
public ABase<B> GetA(MoreSpecificA specificA)
{
return specificA;
}
public B GetB(MoreSpecificA specificA)
{
return specificA.TheB; //compiler complains about ambiguity here
}
}
Then I get the best of both worlds. Does the applicability of this solution depend on whether A
adds anything to ABase
?
Or is my original plan of just hiding the method in the derived type to return a derived type of the original method ok?
Or is my original plan of just hiding the method in the derived type to return a derived type of the original method ok?
So long as it means exactly the same thing, I think it's okay. You can see something like this in the standard libraries, with IDbConnection.CreateCommand
(which returns IDbCommand
) and SqlConnection.CreateCommand
(which returns SqlCommand
) for example.
In that case it's using explicit interface implementation for the IDbConnection
version, but it's the same principle.
You can also see it in IEnumerator<T>.Current
vs IEnumerator.Current
and IEnumerable<T>.GetEnumerator()
vs IEnumerable.GetEnumerator()
.
I would only use it in cases where the implementation for the more weakly-typed method just returns the result of calling the more strongly-typed method though, use implicit conversion. When they actually start doing different things, that becomes much harder to reason about later.