Returning interface but concrete may have properties not on the interface, can I get them with a cast?

1.7k views Asked by At

I have a feeling that my use of an interface is incorrect. I know that an interface is a contract that the concrete class has to adhere to.

So I will explain the problem I am trying to solve and maybe someone can point me in the right direction.

I am building an app that returns a page for any request, I have three page types, Cms, Product and Category.

All three have to implement the following interface:

public interface IPage
    {
        PageType PageType { get; set; }
        PageContent Content { get; set; }
        Meta Meta { get; set; }
    }

These properties are requeried whatever the page type.

a page may have extra properties depending on their type, for example a category page could be like so:

public class CategoryPage : IPage
    {
        public PageType PageType { get; set; }
        public PageContent Content { get; set; }
        public Meta Meta { get; set; }

        public List<Product> Products { get; set; }
    }

At the moment I have a page service that will return a page for the requested url.

Based on the PageType it knows what type of page to return.

The problem is that pageService returns an IPage so that it can return any of the page types.

This is a problem as not all my concretes just implement the interface, in the case of a category page it also has a List, which as you would expect I cant access unless I cast to the concrete type.

But is there a way I can return a generic page type and have the receiver know what concrete it is?

I'm sure that how I am doing it that the moment is not the best way and would like some direction and advice as to how I can solve this little problem.

Thanks

Update

I have settled for a cast.

I'm sure there must be a better way of handling a situation where several classes use some base properties but also implement their own. when you get one of these classes from a service you need to know what you have got back so you can work with the relevant properties..

Or maybe what I am trying to do here is just plain wrong and I need to take another approach. I think I will push on with what I have for now but keep thinking about it.

Update 2

I have changed the way I am doing this so I have no need for a cast, I have a PageType enum that I use to identify the type of page that is being worked with.

This coupled with a Ipage that inherits everything needed seems to be a nice enough solution and removes the need for a cast.

4

There are 4 answers

1
Joachim Isaksson On BEST ANSWER

You can always check if the object you have a reference to is of a particular type using the is keyword;

if(obj is Class1) {

That said, if your design requires you to know concrete types, there is most likely something wrong with the design itself. If there are differences in behavior between the classes, implement the differences inside the classes instead of having to cast them to implement it outside of them.

0
supercat On

Ideally, one would almost never need to use typecasts, especially given the existence of generics. From a pragmatic standpoint, however, sometimes it is better to use typecasts than to go to enormous lengths to avoid them.

In some sense, it is inelegant to have a collection of objects which have differing abilities (or a factory method which will return differing abilities) in cases where it may be necessary to use abilities which exist in some objects but not others. It is certainly possible to say something like

  IInterfaceThatMayOrMayNotBePresent foo = bar as IInterfaceThatMayOrmayNotBePresent;
  if (foo != null)
    foo.MethodOfThatInterface();

but it's a bit of a code smell. It is often good to determine why one needs to use methods which aren't present in all of the instances in the list, and determine whether a better class design might be helpful.

For example, suppose that some types of objects want to be notified if a "Wowzo" happens; other types don't care. Further, one expects to have lists containing objects of both types, and will need to notify notify all the items that care. One could use the above type-checking pattern to notify only those items that need it, but there may be a more efficient approach: one could define two interfaces: IAcceptWowzoNotifications and IActOnWowzoNotifications, with the latter inheriting the first. Classes which implement the first but not the second would be expected to implement Wowzo notifications with empty methods. If every item in the list implements IAcceptWowzoNotifications, whether or not it does anything with such notification, it will be faster to simply notify everyone on the list than to check which items need notification.

Note that this approach is not entirely without cost. First of all, because there is no provision for interfaces to offer default method implementations, every type which implements IAcceptWowzoNotifications will need to define stub methods. Further, if the only reason an item would be put on the list would be to send it Wowzo notifications, and if notification of everyone on the list will be a more frequent occurrence than adding items to the list, it would be better to check whether items implements IActOnWowzoNotifications before adding them to the list, than to blindly add items to the list which may or may not need to be there.

Interface inheritance hierarchies are very powerful and, in many cases, underutilized. The fact that an interface implementation doesn't have to worry about which members came from which ancestor interfaces makes it easy to split interfaces with far less headache than would occur with splitting classes.

1
Justin Niessner On

If your receiver must know the concrete type, then you're not using Interfaces properly.

If you're returning a page, there's really no reason for anything to know what kind of page it is. I would add a Render method to the IPage interface so that all the receiver has to do is call Render() and the page will handle the rest.

2
Maggie On

Here are two ways of checking that your interface returned from a service is your concrete type.

        IPage page;
        if (page is CategoryPage)
        {
           // use type here
        }

        CategoryPage categoryPage = page as CategoryPage;
        if (categoryPage != null)
        {
          // use type here
        }