开发者

Casting question in C#

I have a interface that inherits from IList:

public interface IBase {}
public class Derived : IBase {}
public interface IMyList : IList<IBase> {}

I want to cast a variable of type IMyList to typ开发者_JAVA百科e IList<Derived> or List<Derived>, whichever is easier or makes the most sense. What's the best way to do this?

Note that I'm using .NET 3.5


A direct cast isn't going to be suitable since there are numerous problems that could occur (IMyList may contain types other than Derived, etc.)

As long as you're not modifying the list (in which case, IEnumerable<Derived> would work) you could simply:

var validItems = myList.Cast<Derived>();

And then iterate over the result.

Edit

Based on the OP's comment below, there are two other things to mention:

1) If you need an IList<Derived>, you could simply add to the above and call myList.Cast<Derived>().ToList();

2) If those are truly the requirements, then your code doesn't make any sense. If IMyList should only ever contain Derived objects then IMyList should derive from IList<Derived>. Remember that while you know that there is only one type implementing the IBase interface, the compiler doesn't so be specific.

Using interfaces everywhere just for the sake of using interfaces doesn't help anybody!


I am passing the casted result into a function that takes an IList<Derived>.

Then you are in a world of pain of your own devising. My advice would be first to find some other way to solve your circular dependency problem. Making everything into interfaces that have only one possible implementation is a painful way to solve that problem, as you have discovered. I don't recommend it.

If you can't do that then I would try to fix the offending function so that it either takes an IList<IBase>, an IEnumerable<IBase> or an IEnumerable<Derived>. Preferably one of the IEnumerable solutions; most methods that take lists in fact only need sequences. If you could explain why you need a list here, that would be helpful in trying to find a workaround.

If you can make it take an IList<IBase> or IEnumerable<IBase> then you're done; you already have something in hand that is implicitly convertible to the desired type.

If you can make it take an IEnumerable<Derived> then you can say myListOfIBase.Cast<Derived>() (if you really know that all of them are Derived) or myListOfIBase.OfType<Derived>() (if you suspect that some of them might not be of type Derived and want to skip them) and get an IEnumerable<Derived> that efficiently uses the underlying list.

If you cannot change the offending function then you can make your own class that efficiently uses the underlying list:

sealed class MyProxyList : IList<Derived>
{
    IList<IBase> underlyingList;
    public MyProxyList(IList<IBase> underlyingList)
    {
        this.underlyingList = underlyingList;
    }
    ... now implement every member of IList<Derived> as 
    ... a call to underlyingList with a cast where necessary 
}

And then pass a new MyProxyList to the method.


.Net 3.5 does not have generic covariance. To demonstrate:

[TestFixture]
class Class1
{
    [Test]
    public void test()
    {
        var list = new List<SuperClass>();
        list.Add(new SuperClass());

        var castedList = ((List<BaseClass>)list);
    }
}

public class BaseClass
{
    public string a { get; set; }
}

public class SuperClass : BaseClass
{
    public string b { get; set; }
}

Will not compile successfully.

In Justin Niessner's response, the work around

var validItems = myList.Cast<Derived>();  

was posted. This will work but will result in an enumeration of the entire list (although this enumeration is deferred) and also return an enumerable. To convert the list and end up with an IList you can use the following

[Test]
public void CanConvertListToBaseClass()
{
    var list = new List<SuperClass>();
    list.Add(new SuperClass());

    var castedList = list.Cast<BaseClass>().ToList();
    Assert.That(castedList, Is.InstanceOf<IList<BaseClass>>());
}

This is a pretty brute-force approach however. This will result in a new and separate IList and will force an enumeration of the entire list.


Since IList<T> allows read and write operations of type T, the interface is neither co- nor contravariant. Thus, what you want cannot be done. Imagine the following code:

var myList = new MyListImlementation();
myList.Add(new BaseImplementation());
var castList = (IList<Derived>)myList; // this is what you want

// this would break, because myList contains elements of type BaseImplementation.
Derived d = castList[0];               


If you can be absolutely certain going forward that all instances will be of type Derived, then you can use Cast<>(), with the performance issues noted earlier. If there is a chance that something besides Derived will ever be in the list, then you'll want to use OfType<Derived>() instead. Non-Derived items would be excluded instead of throwing.


You can't do this in .Net 3.5, but you can in .Net 4.0.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜