Reputation: 4618
I seem to be having a basic syntax problem with an interface implementation. Basically I have this:
public interface IMarkerInterface
{
}
public class ConcreteObject : IMarkerInterface
{
}
public interface IDoStuffInterface
{
void DoStuff(IMarkerInterface obj);
// also doesn't work
// void DoStuff<T>(T obj) where T : IMarkerInterface;
}
public class ConcreteDoStuff : IDoStuffInterface
{
public void DoStuff(ConcreteObject c)
{
}
}
To my mind, ConcreteObject
implements IMarkerInterface
, so therefore ConcreteDoStuff.DoStuff()
should implement IDoStuffInterface
.
But I get a compilation error "Error ConcreteDoStuff does not implement interface IDoStuffInterface.DoStuff()"
How come?
Upvotes: 2
Views: 215
Reputation: 50316
If I give you any object implementing IDoStuffInterface
, then you expect to be able to call a method DoStuff
on it with as its argument any object that implements IMarkerInterface
, right? However, if what you want would be possible, then you could somehow (invisible to you as user of the interface) only call DoStuff
with a ConcreteObject
, but not any other object implementing IMarkerInterface
. Therefore, what you want is not really possible (nor desired, as it violates the Liskov Substitution Principle).
However, what you want can be done writing an explicit interface member implementation that implements the interface's DoStuff(IMarkerInterface)
method, while providing a normal method DoStuff(ConcreteObject)
implementation next to it. Any user of the interface will only see and be able to call the DoStuff(IMarkerInterface)
method, but any user of ConcreteDoStuff
will only directly be able to call the DoStuff(ConcreteObject)
method. If the user wants to call the interface method anyway, the object must be cast to IDoStuffInterface
first.
public class ConcreteDoStuff : IDoStuffInterface
{
// Explicit interface member implementation:
// This method is not directly visible as a member of the class.
void IDoStuffInterface.DoStuff(IMarkerInterface obj)
{
// Do something with 'obj', or throw an exception when
// it has the wrong type. Delegate the call to the
// other DoStuff method if you wish.
}
// Normal method, technically not related to the interface method:
public void DoStuff(ConcreteObject c)
{
// Do your thing.
}
}
Edit:
The technique of explicitly implementing an interface is well known, well understood and quite common. It is not considered to be a hack, and does not have to be avoided. It is even required in the rare case that a class implements two interfaces that both define a member with the same name, but you want to give them different behaviors.
However, in all cases where you limit the allowed input values or actions, you violate the Liskov Substitution Principle (LSP). In my example, you would limit the IMarkerInterface obj
parameter to ConcreteObject
objects. Other examples include: a Collection<T>
that throws an exception when a null
object is added; an IComparable.CompareTo
implementation that throws an error when the argument has the wrong type; or a ReadOnlyCollection
that throws an exception when calling its Add
method.
While the LSP should not be violated (so the code becomes more (re)usable and testable), it is violated quite often, even in the .NET Framework itself. Not violating it in some cases may be cumbersome, result in hard-to-read code, or may even be impossible (e.g. due to restrictions of the classes and interfaces of the .NET Framework).
As I said, the same 'workaround' is applied to implementations of IComparable
. For example, to implement a class that may only be compared to other objects of the same type:
public class BeanBag : IComparable, IComparable<BeanBag>
{
private int beanCount;
// Explicit interface member implementation:
int IComparable.CompareTo(object other)
{
if (!(other is BeanBag))
throw new ArgumentException("Wrong type!");
// Calls the normal CompareTo() method.
return CompareTo((BeanBag)other);
}
// Normal CompareTo method:
public int CompareTo(BeanBag other)
{
if (other == null) return 1;
return this.beanCount.CompareTo(other.beanCount);
}
}
Upvotes: 4
Reputation: 1509
By only accepting ConcreteObject
in your implementation of ConcreteDoStuff.DoStuff()
you have limited the type of arguments that can be passed in.
If you were to create another class that also implemented the IMarkerInterface
:
public class ConcreteObject2 : IMarkerInterface
{
}
It should be able to be passed in to any DoStuff()
implementation, but ConcreteObject2
is not a ConcreteObject
by definition, therefore the contract will bi violated, as the other answers have indicated.
Upvotes: 2
Reputation: 9494
You're best off using a generic interface:
public interface IMarkerInterface
{
}
public class ConcreteObject : IMarkerInterface
{
}
public interface IDoStuffInterface<T>
{
void DoStuff(T obj);
}
public class ConcreteDoStuff : IDoStuffInterface<ConcreteObject>
{
public void DoStuff(ConcreteObject c)
{
}
}
Upvotes: 1
Reputation: 2544
Your implemented methods need to have the exact same signature as the interface. While all 'ConcreteObject' objects are of type 'IMarkerInterface', not all 'IMarkerInterfaces' will be 'ConcreteObject'. Thus the two signatures are not equivalent. Interfaces must be able to guarantee the CLR that any object of that type has a valid method implemented.
Upvotes: 5
Reputation: 100527
You need to implemet method with matching signature:
public class ConcreteDoStuff : IDoStuffInterface
{
public void DoStuff(IMarkerInterface c) // not ConcreteObject c
{
}
}
Note you still can call ConcreteDoStuff.DoStuff with an instance of ConcreteObject :
var concrete = new ConcreteObject()
new ConcreteDoStuff().DoStuff(concrete);
You can also do this (explicit implementation of interface) if it works better for you:
public class ConcreteDoStuff : IDoStuffInterface
{
public void DoStuff(ConcreteObject c)
{
}
void IDoStuffInterface.DoStuff(IMarkerInterface c)
{
// some implementation preferably related to
// DoStuff(ConcreteObject) i.e.:
DoStuff(c as ConcreteObject);
}
}
Upvotes: 2
Reputation: 273229
Your implementing class, ConcreteDoStuff
, is trying to change the contract. Unilaterally.
Upvotes: 2