C # - Não é possível converter implicitamente o tipo List <Product> em List <IProduct>

89

Tenho um projeto com todas as minhas definições de interface: RivWorks.Interfaces
Tenho um projeto onde defino implementações concretas: RivWorks.DTO

Já fiz isso centenas de vezes, mas por algum motivo estou recebendo este erro agora:

Não é possível converter implicitamente o tipo 'System.Collections.Generic.List <RivWorks.DTO.Product>' em 'System.Collections.Generic.List <RivWorks.Interfaces.DataContracts.IProduct>'

Definição de interface (abreviada):

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; }
        ...
    }
}

Definição de classe concreta (abreviada):

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
    }
}

Em outra aula eu tenho:

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>();
    ...

Alguma ideia de por que isso pode estar acontecendo? (E tenho certeza de que é algo trivialmente simples!)

Keith Barrows
fonte

Respostas:

113

Sim, é uma limitação de covariância em C #. Você não pode converter uma lista de um tipo em uma lista de outro.

Ao invés de:

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

Você tem que fazer isso

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

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

Eric Lippert explica por que eles o implementaram desta forma: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

(E por que é diferente de trabalhar com matrizes de itens).

kemiller2002
fonte
Daniel e Kevin responderam a isso, embora de maneiras diferentes. Bravo! Explicação justa de contra / covariância. Na verdade, é IN ou OUT, mas não ambos. Não tendo usado o .NET 4.0, isso me escapou completamente! Obrigado rapazes.
Keith Barrows,
39

Você não pode fazer isso. Se você tiver um List<IProduct>, pode colocar qualquer IProduct nele. Portanto, se você tiver um Product2que implementa, IProductpode colocá-lo na lista. Mas a lista original foi criada como List<Product>, portanto, qualquer pessoa que usar a lista esperaria que apenas objetos do tipo Product, não Product2estivessem na lista.

No .NET 4.0, eles adicionaram covariância e contravariância para interfaces, para que você pudesse converter IEnumerable<Product>para IEnumerable<IProduct>. Mas isso ainda não funciona para listas, já que a interface de lista permite que você "coloque coisas" e "tire coisas".

Daniel Plaisted
fonte
8
Boa dica sobre como usar o IEnumerable
Lee Englestone
Ainda estou um pouco confuso com esta parte:> então, qualquer pessoa que usar a lista esperaria que apenas objetos do tipo Produto, não Produto2, estivessem na lista. Por que o usuário faria essa suposição? Se estou usando algo declarado como List <IProduct>, não esperaria ser capaz de fazer downcast automaticamente de elementos para uma implementação ou outra. (É possível que seja apenas uma escolha peculiar da MSFT de que não gosto, mas, se não gostar, realmente quero tentar entender o raciocínio deles.)
Eleanor Holley
5

Apenas como uma observação: covariância e contravariância em genéricos foram adicionadas no C # 4.0.

Danvil
fonte
1
Mas não é compatível com construções que têm canais IN e OUT. List <T> é uma construção assim eles não suportam contra / covariância!
Keith Barrows,
Sim, só funcionaria se usar um IEnumerable <contract.IProduct>
Danvil
4

Bem, você pode usar isso!

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

Agora, para dar uma solução direta,

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
fonte
2

Este é um pequeno exemplo de como fazer.

    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
fonte
-3

Em vez disso, tente isto:

List<contracts.IProduct> myList = new List<contracts.IProduct>((new List<dto.Product>()).Cast<contracts.IProduct>());
Hoppy
fonte
7
seriamente? você sabe o que isso significa?
Nix