开发者

Compile-time restrictions: Allow As but not Bs where B:A

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:IAmNotA开发者_如何转开发nIBar {...}

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 allowing IFoos, that tells me that IBar should not really inherit from IFoo (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.


This is a strange design. Not sure why you wouldn't want to allow this, but since you want it.

You could just make IFoo not implement IBar, and then have the implementing classes implement both. If it is critical that IBar have the same methods as IBar, just copy them in to IFoo and the implement classes should be satisfied.


Assuming you want to disallow any derived class, this makes no sense. You are then basically saying that only IFoo is allowed, in which case there is no need for a generic class.


Then where T:IFoo is not correct. Consider breaking up your interface definitions into smaller contracts and then have Snafu<T> constraint T to only those interfaces that is actually supports.


It should not be possible because IBar is an IFoo.

If you are worried that someone deriving from your class might changed some behavior, maybe consider using sealed classes.


It's irrelevant whether IBar inherits from IFoo. There is no way at compile time to specify that an object must not support an interface. Your best bet may be to have a sealed version of any classes that aren't going to support your interface, and have them support an interface like the following (shown with VB syntax to avoid HTMLish munging of angle brackets):

Interface ISelf(Of Out T As Class)
  Function Self() As T
End Interface
Interface INonBar(of Out T)
  Inherits ISelf(of Out T As Class)
End Interface

Note that I recommend defining a single ISelf interface, which may then be inherited by other interfaces, so as to avoid having multiple interfaces all defining a "Self" method. A method which wants to accept a Foo that doesn't implement Bar would accept a parameter of type INonBar(of Foo); one could use the Self method to effectively recast it as a Foo.

Incidentally, this approach also gets aorund the limitation that there's no way to specify that a field is an object that implements two unrelated interfaces, or is a derivative of some class which implements an interface which the class itself doesn't implement. The latter specifications may be achieved in function parameters, but with the annoying limitation that there's no way to store the parameters in fields or other objects in such a way that they can be retrieved and passed to similar functions (absent reflection, it's not even possible to typecast a field that is known to satisfy two type constraints in such a way that it can be passed to a function which requires that a parameter satisfy two type constraints).

Incidentally, while the particular pattern you're looking for is unusual, there are other cases where it's useful to have objects which don't follow a regular inheritance pattern. For example, object Foo might derive Bar, which in turn derives Boz. Objects of type "Foo" could be cloned without breakage, as could those of type "Bar", but objects of type "Boz" could not. Defining ISelf(T) as above along with ICloneable(Of T), one could define sealed types CloneableFoo and CloneableBar, and have a routine which could accept either (by having it accept an object of type ICloneable(Of Foo) [not ICloneable(Of CloneableFoo)!] The pattern's a little ugly, but it's better than having Foo support .Clone, and having Boz.Clone throw a NotSupportedException.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜