C # - nie można niejawnie przekonwertować typu List <Product> na List <IProduct>

89

Mam projekt ze wszystkimi moimi definicjami interfejsu: RivWorks.Interfaces
Mam projekt, w którym definiuję konkretne implikacje: RivWorks.DTO

Robiłem to już setki razy, ale z jakiegoś powodu teraz pojawia się ten błąd:

Nie można niejawnie przekonwertować typu „System.Collections.Generic.List <RivWorks.DTO.Product>” na „System.Collections.Generic.List <RivWorks.Interfaces.DataContracts.IProduct>”

Definicja interfejsu (skrócona):

namespace RivWorks.Interfaces.DataContracts
{
    public interface IProduct
    {
        [XmlElement]
        [DataMember(Name = "ID", Order = 0)]
        Guid ProductID { get; set; }
        [XmlElement]
        [DataMember(Name = "altID", Order = 1)]
        long alternateProductID { get; set; }
        [XmlElement]
        [DataMember(Name = "CompanyId", Order = 2)]
        Guid CompanyId { get; set; }
        ...
    }
}

Concrete class definition (shortened):

namespace RivWorks.DTO
{
    [DataContract(Name = "Product", Namespace = "http://rivworks.com/DataContracts/2009/01/15")]
    public class Product : IProduct
    {
        #region Constructors
        public Product() { }
        public Product(Guid ProductID)
        {
            Initialize(ProductID);
        }
        public Product(string SKU, Guid CompanyID)
        {
            using (RivEntities _dbRiv = new RivWorksStore(stores.RivConnString).NegotiationEntities())
            {
                model.Product rivProduct = _dbRiv.Product.Where(a => a.SKU == SKU && a.Company.CompanyId == CompanyID).FirstOrDefault();
                if (rivProduct != null)
                    Initialize(rivProduct.ProductId);
            }
        }
        #endregion

        #region Private Methods
        private void Initialize(Guid ProductID)
        {
            using (RivEntities _dbRiv = new RivWorksStore(stores.RivConnString).NegotiationEntities())
            {
                var localProduct = _dbRiv.Product.Include("Company").Where(a => a.ProductId == ProductID).FirstOrDefault();
                if (localProduct != null)
                {
                    var companyDetails = _dbRiv.vwCompanyDetails.Where(a => a.CompanyId == localProduct.Company.CompanyId).FirstOrDefault();
                    if (companyDetails != null)
                    {
                        if (localProduct.alternateProductID != null && localProduct.alternateProductID > 0)
                        {
                            using (FeedsEntities _dbFeed = new FeedStoreReadOnly(stores.FeedConnString).ReadOnlyEntities())
                            {
                                var feedProduct = _dbFeed.AutoWithImage.Where(a => a.ClientID == companyDetails.ClientID && a.AutoID == localProduct.alternateProductID).FirstOrDefault();
                                if (companyDetails.useZeroGspPath.Value || feedProduct.GuaranteedSalePrice > 0)     // kab: 2010.04.07 - new rules...
                                    PopulateProduct(feedProduct, localProduct, companyDetails);
                            }
                        }
                        else
                        {
                            if (companyDetails.useZeroGspPath.Value || localProduct.LowestPrice > 0)                // kab: 2010.04.07 - new rules...
                                PopulateProduct(localProduct, companyDetails);
                        }
                    }
                }
            }
        }
        private void PopulateProduct(RivWorks.Model.Entities.Product product, RivWorks.Model.Entities.vwCompanyDetails RivCompany)
        {
            this.ProductID = product.ProductId;
            if (product.alternateProductID != null)
                this.alternateProductID = product.alternateProductID.Value;
            this.BackgroundColor = product.BackgroundColor;
            ...
        }
        private void PopulateProduct(RivWorks.Model.Entities.AutoWithImage feedProduct, RivWorks.Model.Entities.Product rivProduct, RivWorks.Model.Entities.vwCompanyDetails RivCompany)
        {
            this.alternateProductID = feedProduct.AutoID;
            this.BackgroundColor = Helpers.Product.GetCorrectValue(RivCompany.defaultBackgroundColor, rivProduct.BackgroundColor);
            ...
        }
        #endregion

        #region IProduct Members
        public Guid ProductID { get; set; }
        public long alternateProductID { get; set; }
        public Guid CompanyId { get; set; }
        ...
        #endregion
    }
}

In another class I have:

using dto = RivWorks.DTO;
using contracts = RivWorks.Interfaces.DataContracts;
...
public static List<contracts.IProduct> Get(Guid companyID)
{
    List<contracts.IProduct> myList = new List<dto.Product>();
    ...

Any ideas why this might be happening? (And I am sure it is something trivially simple!)

Keith Barrows
źródło

Odpowiedzi:

114

Yep it's a covariance limitation in C#. You can't convert a list of one type to a list of another.

Instead of:

List<contracts.IProduct> myList = new List<dto.Product>();

You have to do this

List<contracts.IProduct> myList = new List<contracts.IProduct>();

myList.Add(new dto.Product());

Eric Lippert explains why they implemented it this way: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

(And why it is different than working with arrays of items).

kemiller2002
źródło
Both Daniel and Kevin answered this, though in differing ways. Bravo! Fair explanation of contra/covariance. It is indeed either IN or OUT but not both. Having not used .NET 4.0 that slipped my mind completely! Thanks guys.
Keith Barrows
39

You can't do that. If you have a List<IProduct>, you can put any IProduct in it. So if you have a Product2 which implements IProduct you could put it in the list. But the original list was created as List<Product>, so anyone using the list would expect only objects of type Product, not Product2 to be in the list.

In .NET 4.0, they added covariance and contravariance for interfaces, so you could convert IEnumerable<Product> to IEnumerable<IProduct>. But this still doesn't work for lists, since the list interface allows you both to "put stuff in" and "get stuff out".

Daniel Plaisted
źródło
8
Nice tip about being able to use IEnumerable
Lee Englestone
I'm still a little confused by this part: > so anyone using the list would expect only objects of type Product, not Product2 to be in the list. Why would the user make that assumption? If I am using something declared as List<IProduct>, I wouldn't expect to automatically be able to downcast elements to one implementation or another. (It's possible this is just a quirky choice I don't like by MSFT, but if not I really want to try to understand their reasoning.)
Eleanor Holley
5

Just as a remark: Covariance and Contravariance in Generics was added in C# 4.0.

Danvil
źródło
1
But not supported in constructs that have both IN and OUT channels. List<T> is such a construct so they do not support contra/covariance!
Keith Barrows
Yes, it would only work if using an IEnumerable<contracts.IProduct>
Danvil
4

Well, you can use this!

        class A {}
        class B : A {}
        ...
        List<B> b = new List<B>();
        ...
        List<A> a = new List<A>(b.ToArray());

Now, to give direct solution,

using dto = RivWorks.DTO;
using contracts = RivWorks.Interfaces.DataContracts;
...
public static List<contracts.IProduct> Get(Guid companyID) {
    List<dto.Product> prodList = new List<dto.Product>();
    ...
    return new List<contracts.IProduct>(prodList.ToArray());
}
Nayan
źródło
2

This is a little example how to do it.

    public void CreateTallPeople()
    {
        var tallPeopleList = new List<IPerson>
        {
            new TallPerson {Height = 210, Name = "Stevo"},
            new TallPerson {Height = 211, Name = "Johno"},
        };
        InteratePeople(tallPeopleList);
    }

    public void InteratePeople(List<IPerson> people)
    {
        foreach (var person in people)
        {
            Console.WriteLine($"{person.Name} is {person.Height}cm tall.  ");
        }
    }
Jimmy
źródło
-3

Try this instead:

List<contracts.IProduct> myList = new List<contracts.IProduct>((new List<dto.Product>()).Cast<contracts.IProduct>());
Hoppy
źródło
7
seriously? do you know what that even means ?
Nix