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

≯℡__Kan透↙ 提交于 2019-12-12 16:36:31

问题


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.


回答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.




回答2:


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.



来源:https://stackoverflow.com/questions/9022811/c-net-3-5-casting-objects-that-extend-generic-lists

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!