C #: no se puede convertir implícitamente el tipo List <Product> en List <IProduct>

89

Tengo un proyecto con todas mis definiciones de Interface: RivWorks.Interfaces
Tengo un proyecto donde defino implicaciones concretas: RivWorks.DTO

He hecho esto cientos de veces antes, pero por alguna razón recibo este error ahora:

No se puede convertir implícitamente el tipo 'System.Collections.Generic.List <RivWorks.DTO.Product>' a 'System.Collections.Generic.List <RivWorks.Interfaces.DataContracts.IProduct>'

Definición de interfaz (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; }
        ...
    }
}

Definición de clase 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
    }
}

En otra clase tengo:

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

Alguna idea de por qué esto esta pasando? (¡Y estoy seguro de que es algo trivialmente simple!)

Keith Barrows
fuente

Respuestas:

114

Sí, es una limitación de covarianza en C #. No puede convertir una lista de un tipo en una lista de otro.

En vez de:

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

Tienes que hacer esto

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

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

Eric Lippert explica por qué lo implementaron de esta manera: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

(Y por qué es diferente a trabajar con matrices de elementos).

kemiller2002
fuente
Tanto Daniel como Kevin respondieron a esto, aunque de diferentes maneras. ¡Bravo! Explicación justa de la contra / covarianza. De hecho, es IN o OUT, pero no ambos. ¡No haber usado .NET 4.0 se me olvidó por completo! Gracias chicos.
Keith Barrows
39

No puedes hacer eso. Si usted tiene una List<IProduct>, puede poner ninguna IProduct en ella. Entonces, si tiene un Product2implemento IProduct, puede ponerlo en la lista. Pero la lista original se creó como List<Product>, por lo que cualquiera que use la lista esperaría que solo los objetos de tipo Product, no Product2estén en la lista.

En .NET 4.0, agregaron covarianza y contravarianza para interfaces, por lo que podría convertir IEnumerable<Product>a IEnumerable<IProduct>. Pero esto todavía no funciona para las listas, ya que la interfaz de lista le permite tanto "poner cosas dentro" como "sacar cosas".

Daniel Plaisted
fuente
8
Buen consejo sobre poder usar IEnumerable
Lee Englestone
Todavía estoy un poco confundido por esta parte:> por lo que cualquiera que use la lista esperaría que solo los objetos de tipo Producto, no Product2 estén en la lista. ¿Por qué el usuario haría esa suposición? Si estoy usando algo declarado como List <IProduct>, no esperaría poder reducir automáticamente los elementos a una implementación u otra. (Es posible que esta sea solo una elección peculiar que no me gusta de MSFT, pero si no, realmente quiero tratar de entender su razonamiento)
Eleanor Holley
5

Solo como comentario: la covarianza y contravarianza en genéricos se agregó en C # 4.0.

Danvil
fuente
1
Pero no se admite en construcciones que tienen canales IN y OUT. List <T> es una construcción de este tipo, por lo que no admiten contra / covarianza.
Keith Barrows
Sí, solo funcionaría si se usa un IEnumerable <contract.IProduct>
Danvil
4

Bueno, ¡puedes usar esto!

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

Ahora, para dar una solución directa,

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

Este es un pequeño ejemplo de cómo hacerlo.

    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.  ");
        }
    }
Palanqueta
fuente
-3

Prueba esto en su lugar:

List<contracts.IProduct> myList = new List<contracts.IProduct>((new List<dto.Product>()).Cast<contracts.IProduct>());
Lúpulo
fuente
7
¿seriamente? ¿Sabes lo que eso significa?
Nix