简体   繁体   中英

Cast unknown object to generic interface of interface (from initially explicit generic type of generic collection type of type)

Basically I got those sample classes:

public interface IHasParts<TCollectionType> : where TCollectionType : ICollection
{
  TCollectionType Parts { get; set; }
}

public class CarPart
{
  //...
}

public class Car : IHasParts<List<CarPart>>
{
  public List<CarPart> Parts { get; set; }
  //...
}

Yes, I need to use an generic interface of ICollection here, because classes that implement IHasParts need different list types of Parts based on some hard programmed conditions.

Now I get an unknown object of ie Car and I need to cast it to the highest parent that still has the Parts property available:

Car c = new Car() {
  Parts = new List<CarPart>() {
    // ...
  }
};

object o = (object)c;

int partsCount = ((IHasParts<ICollection>)o).Parts.Count; // InvalidCastException

How can I do that? DotNetFiddle

This is a variance issue.

You're assuming that, because List<T> is a subtype of ICollection , then IHasParts<List<T>> must too be a subtype of IHasParts<ICollection> . It doesn't.

If you want IHasParts<A> to be a subtype of IHasParts<B> where A is a subtype of B , then you need to make IHasParts covariant in its type parameter T (using the out keyword).

public interface IHasParts<out TCollectionType> : where TCollectionType : ICollection
{
     TCollectionType Parts { get; }
}

For a type to be covariant, T can only be used in covariant positions : method return types, get-only property types and get-only indexers.

It can no longer be used in contravariant positions: method arguments, property/indexer setters.

If you define your Car class with ICollection instead of List<CarPart> , then works:

public class Car : IHasParts<ICollection>
{
    public ICollection Parts { get; set; }
}

You can still initialize your Parts with a List<CarPart>

Add an abstract class to take care of specifying the ICollection type. Declare your code something like this:

public interface IHasParts
{
    ICollection Parts { get; }
}

public abstract class HasParts<TCollectionType, TPartType> : IHasParts where TCollectionType : ICollection
{

    public TCollectionType Parts;


    ICollection IHasParts.Parts { get { return this.Parts; } }

}

public class CarPart
{
    //...
}

public class Car : HasParts<List<CarPart>, CarPart>
{
    protected void AddParts()
    {
        this.Parts.Add(new CarPart());
    }
}

UPDATE:

Here is an updated version of your DotNetFiddle: https://dotnetfiddle.net/O3JZgc

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM