C#/.NET 3.5: Casting objects that extend generic lists
Asked Answered
V

2

2

I'm exposing an API through a facade with methods returning interface types, and I have run into an issue with generic lists and inheritance in C#/.NET 3.5.

I have two interfaces:
IMyList (implements IList<IMyItem>)
IMyItem

And three classes:
MyList (implements IMyList, extends List<MyItem>)
MyOtherList (implements IMyList, extends ObservableCollection<MyItem>)
MyItem (implements IMyItem)

But it doesn't seem possible. How should I go about exposing only what is necessary, but still call the right implementations of the methods (that can vary for instance between MyList and MyOtherList)?

EDIT:
My facade is a factory looking something like this:

public static class Facade {
    public static IMyList<IMyItem> CreateList() {
        return new MyList<MyItem>();
    }

    public static IMyItem CreateItem() {
        return new MyItem();
    }

    public static IConfiguration CreateConfiguration() {
        return new Configuration();
    }
} 

Usage:

var list = Facade.CreateList();
list.DoSomethingOnChildren();

Now I'm expecting DoSomethingOnChildren() implemented in MyList to execute on a series of MyItem objects. If I was to change the method to return:

public static IMyList<IMyItem> CreateList() {
    return new MyOtherList<MyOtherItem>();
}

I would expect DoSomethingOnChildren() implemented in MyOtherList to execute on a series of MyOtherItem objects.

Visitant answered 26/1, 2012 at 17:50 Comment(4)
Use "return new MyList<IMyItem>();" This is a covariance issue that you're trying to do which isn't supported in C#.Dorothy
But what if I have a MyList<MyOtherItem> for instance where MyOtherItem implements IMyItem. How can I cast and return this list properly?Visitant
Create a MyList<IMyItem> and then populate it with instances of either MyItem or MyOtherItem. If that is not possible, then you will need to use the LINQ .Cast (or a functional equivilant). This would be something like "new MyList<MyItem>.Cast<IMyItem>().ToList()" Note that this is going through every item in that list and casting it, which is what you need to do since covariance isn't supported.Dorothy
Say MyOtherList<IMyItem> had certain methods that depended on methods only exposed in MyOtherItem, how would I go about that, then? My thought was to work with methods unique to MyOtherItem in MyOtherList<MyOtherItem> and then return it as a IMyList<IMyItem>, but perhaps this is fundamentally wrong?Visitant
H
1

Just because MyItem implements IMyItem doesn't mean MyList<MyItem> implements IMyList<IMyItem>. Put it this way, assume you had:

IList<Shape> s = new List<Rectangle>();

IF this were allowed, it would cause tons of problems because it would let you do:

s.Add(new Circle());

Since Circle is also a Shape. Some generic interfaces support co/contra-variance because the generic type parameter is used only in an in or out fashion, because in IList<T> however, the T argument is used both in in and out positions, this makes it difficult, and C# doesn't support it.

So, you can't convert the references, but you could load the members of an MyList<MyItem> into an IMyList<IMyItem> through other means (LINQ, etc) and return a new instance of the list.

If you wanted to support the two interface, but also allow a specific implementation, you could use explicit interface implementation.

UPDATE:

So, if you want to do something similar, you could do this instead. Have your interface return back the more generic implementation, then have your implementing classes have an explicit implementation to return only a generic implementation, and an overload for the specific implementation.

So something like:

// purely for illustrative purposes
public interface IShape { }
public class Rectangle : IShape { }

// represents your more "generic" interface
public interface ShapeMaker
{
    List<IShape> GetShapes();
}

// Your specific implementation
public class RectangleMaker : ShapeMaker
{
    // the explicit implementation of the interface satisfies the 
    // original, and behaves like original when called from an ShapeMaker 
    // interface reference
    List<IShape> ShapeMaker.GetShapes()
    {
        return new List<IShape>();
    }

    // but, we also provide an overload that returns a more specific version
    // when used with a reference to our subclass.  This gives us more 
    // functionality.
    public List<Rectangle> GetShapes()
    {
        return new List<Rectangle>();
    }
}

You can only do this with explicit interface implementation, because you must still satisfy the original interface (must have same return type). But, it also allows us to say if used from the subclass reference, it can use the more specific method instead.

Heathenish answered 26/1, 2012 at 18:3 Comment(11)
Okay, I see, that would make for an inconsistent list... So in short I suppose I will have to store everything as IMyItem and then, in a subclass, cast the items I need to operate on to the expected type? Or do you figure there is any other way of getting around this issue?Visitant
There are many ways to do it, depends largely on what you want to do, it looks like in the case you list you're just returning a new instance, is that list held in the class? Some more code might help me fine tune my answer.Heathenish
Yes, it's empty. I just want to expose parts of my list with a certain implementation. If I populated IMyList<IMyItem> using a MyList<MyItem>, how will it know which implementations of the methods to call? How will the interface know where to find the method implementations I mean. How would such a cast look? I have tried the following but it didn't work: return (IMyList<IMyItem>)new MyList<MyItem>().Cast<IMyItem>().ToList(); [Although this feels a bit weird, since it will be casting from a List to a more specific implementation].Visitant
The goal being to work within the list with the more detailed type, but expose only what the list and list items expose through their interfaces.Visitant
If all you are doing is returning a new instance of the list, there is no good way to do that as you want. You can either return an MyList<IMyItem> -- that is, return the specific implementation of the list, but not of the item -- or you'd need a new method for each sub-class. There's just no good way to return a new MyList<MyItem> as an IMyList<IMyItem> because they aren't congruent.Heathenish
If you post more of the code (the relationship between GetList() and the list vs item is a little fuzzy) we may be able to help you find an alternate solution, though.Heathenish
Say MyOtherList<IMyItem> had certain methods that depended on methods only exposed in MyOtherItem, how would I go about that? My thought was to work with methods unique to MyOtherItem in MyOtherList<MyOtherItem> and then return it as a IMyList<IMyItem>, but perhaps this is fundamentally wrong? In short I want to be able to change the functionality of one of these lists without breaking the API, hence I'm trying to avoid exposing any code related to one specific implementation of the list to the people that use the factory method to create the list.Visitant
You can't, simply (in the way you are wanting). You can't expose a List<A> as a List<B> even if A inherits from B because list is both in/out and it would cause some bad behavior. You'd have to load the List<A> into a new List<B>, but really this buys you nothing because it's an empty list and you might as well just have exposed a List<B> to begin with.Heathenish
You can however have an explicit implementation for your interface, and a newer implementation in the class that returns the specific.Heathenish
I have updated my example again in an attempt to illustrate how I'm thinking in terms of limiting underlying types by returning them as a interface types. MyList and MyOtherList both involve logic that I would lose in using naked List<T>-implementations. I guess I would need to modify my list implementations themselves then, and manually wrapping each method that returns/takes an IMyItem as a parameter in, say, ObservableCollection<T>, to make them explicit, and then add other methods with the more detailed return type where need be to use behind the facade?Visitant
Solved it by having explicit interface implementations, it wasn't that many to implement after all, and most of them just overloaded the standard ones with typecasts - thanks!Visitant
D
0

Either write

function IMyList<MyItem> GetList() {
     return new MyList<MyItem>();
} 

or

function IMyList<IMyItem> GetList() {
     return new MyList<IMyItem>();
} 

Why is that? Let's assume that you have two implementations of IMyItem: MyItemA and MyItemB.

If IMyList<IMyItem> and IMyList<MyItemX> were compatible, then GetList could return a IMyList<IMyItem> for a list created as new MyList<MyItemA>().

function IMyList<IMyItem> GetList() {
     return new MyList<MyItemA>();
} 

Then you could do

IMyList<IMyItem> result = GetList();
result[i] = new MyItemB();

However the list is a list of MyItemA objects! Therefore this is forbidden.


EDIT:

Note that arrays unfortunately allow this

string[] strings = { "a", "b", "c" };
object[] objects = strings;
objects[0] = 5;

This will compile. However, it will generate this runtime error:

An unhandled exception of type 'System.ArrayTypeMismatchException' occurred in test.exe

Additional information: Attempted to access an element as a type incompatible with the array.

Dur answered 26/1, 2012 at 18:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.