In the below hierarchy, is there a way at compile-time to exclude IBars while still allowing IFoos?
Example:
//IFoo defines functionality that Snafu needs to use, and so is a type restriction.
public interface IFoo {...}
//IBar defines supplemental required functionality that Snafu does not support.
public interface IBar:IFoo {...}
//Can I generate a compiler error if an IBar is used as the generic type?
public class Snafu<T> where T:IFoo
{
public void DoSomethingWith(T myFoo)
{
//Best thing I can think of with the current hierarchy
if(myFoo is IBar) throw new ArgumentException("IBars are not supported");
}
}
The only thing I can think of that works is to define a third “flag” interface, and apply it to all IFoos that are not IBars. With IFoo and IBar defined:
public interface IAmNotAnIBar:IFoo {}
public class Snafu<T> where T:IAmNotAnIBar {...}
However, this smells like a hack; the new interface doesn’t define anything new that IFoo doesn’t, and developers have to know not to use IFoo (IIRC you can’t hide IFoo by making it internal, while still exposing the other interfaces as public), so they can bypass the compile-time check in good or bad faith by just implementing IFoo. Is there anything more elegant?
EDIT: Thanks to all for the responses so far. Maybe a more concrete example would illustrate why I asked the question in the first place.
Let’s say that IFoo defines basic functionality of a domain object that is mapped to the DB. It must have a read-write ID property, maybe the ability to emit and absorb a DTO, whatever. There are several areas of the system that deal with these object as IFoos, including persistence: IFoos are read and written to persistent storage using a Repository that can handle any IFoo (the class Snafu in the above example; DoSomething() performs some persistent action on a strongly-typed IFoo).
Now, let’s define IBar as a domain object that is populated differently from IFoos. They define, in addition to IFoo functionality, some additional Initialize() method that ensures they are in a consistent state. They are still domain objects (IFoos) and should still be treated as such in all areas of the system, EXCEPT that you cannot pass an IBar to an instance of Snafu and expect correct results, because Snafu does not and cannot call the method properly (let’s say Initialize() takes a parameter that is an external dependency which Snafu doesn’t have and should not be given). Instead, you should call a different class that is the Repository for all IBars. My goal was, at compile-time, to alert the developer that they were doing something wrong, instead of relying on adequate run-time testing (unit, integration, functional, manual, etc) of every possible call to DoSomething() on Snafu to ensure it’s never passed an IBar.
If you’re trying to Exclude
IBars while allowingIFoos, that tells me thatIBarshould not really inherit fromIFoo(they’re not truly a parent -> child relationship).Remember that just because two interfaces share members of the same name it doesn’t mean that one should inherit from the other. If you use inheritance like that, you’re giving a new meaning to the members inherited from the parent thus violating the Liskov Substitution Principle. In your case, it’s obvious that IBar IS NOT also an IFoo…otherwise you wouldn’t need to restrict their use.
My guess is there’s a way to refactor your interfaces so that things make a little more sense but without more details, it’s hard for me to say.