C # - Impossible de convertir implicitement le type List <Product> en List <IProduct>

89

J'ai un projet avec toutes mes définitions d'interface: RivWorks.Interfaces
J'ai un projet où je définis des implémentations concrètes: RivWorks.DTO

J'ai déjà fait cela des centaines de fois, mais pour une raison quelconque, j'obtiens cette erreur maintenant:

Impossible de convertir implicitement le type «System.Collections.Generic.List <RivWorks.DTO.Product>» en «System.Collections.Generic.List <RivWorks.Interfaces.DataContracts.IProduct>»

Définition d'interface (raccourcie):

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

Définition de la classe de béton (raccourcie):

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

Dans une autre classe, j'ai:

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

Des idées pourquoi cela pourrait se produire? (Et je suis sûr que c'est quelque chose de trivialement simple!)

Keith Barrows
la source

Réponses:

113

Oui, c'est une limitation de covariance en C #. Vous ne pouvez pas convertir une liste d'un type en liste d'un autre.

Au lieu de:

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

Tu dois faire ca

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

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

Eric Lippert explique pourquoi ils l'ont implémenté de cette manière: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

(Et pourquoi c'est différent de travailler avec des tableaux d'éléments).

kemiller2002
la source
Daniel et Kevin ont tous deux répondu à cela, mais de manière différente. Bravo! Explication juste de la contra / covariance. C'est en effet IN ou OUT mais pas les deux. N'ayant pas utilisé .NET 4.0 cela m'a complètement échappé! Merci les gars.
Keith Barrows
39

Vous ne pouvez pas faire ça. Si vous en avez un List<IProduct>, vous pouvez en mettre un IProduct . Donc, si vous avez un Product2outil, IProductvous pouvez le mettre dans la liste. Mais la liste d'origine a été créée comme List<Product>, donc toute personne utilisant la liste ne s'attendrait qu'à des objets de type Product, et non Product2à être dans la liste.

Dans .NET 4.0, ils ont ajouté la covariance et la contravariance pour les interfaces, afin que vous puissiez convertir IEnumerable<Product>en IEnumerable<IProduct>. Mais cela ne fonctionne toujours pas pour les listes, puisque l'interface de liste vous permet à la fois de "mettre des éléments" et de "sortir des éléments".

Daniel Plaisted
la source
8
Bonne
Je suis encore un peu confus par cette partie:> donc toute personne utilisant la liste s'attendrait à ce que seuls les objets de type Product, pas Product2, soient dans la liste. Pourquoi l'utilisateur ferait-il cette hypothèse? Si j'utilise quelque chose déclaré comme List <IProduct>, je ne m'attendrais pas à être automatiquement en mesure de réduire les éléments vers une implémentation ou une autre. (Il est possible que ce soit juste un choix excentrique que je n'aime pas par MSFT, mais sinon je veux vraiment essayer de comprendre leur raisonnement.)
Eleanor Holley
5

Juste une remarque: la covariance et la contravariance dans les génériques ont été ajoutées en C # 4.0.

Danvil
la source
1
Mais non pris en charge dans les constructions qui ont à la fois des canaux IN et OUT. List <T> est une telle construction donc ils ne supportent pas la contra / covariance!
Keith Barrows
Oui, cela ne fonctionnerait que si vous utilisiez un IEnumerable <contracts.IProduct>
Danvil
4

Eh bien, vous pouvez l'utiliser!

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

Maintenant, pour donner une solution directe,

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
la source
2

Voici un petit exemple comment procéder.

    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
la source
-3

Essayez plutôt ceci:

List<contracts.IProduct> myList = new List<contracts.IProduct>((new List<dto.Product>()).Cast<contracts.IProduct>());
Houblonné
la source
7
sérieusement? savez-vous ce que cela signifie?
Nix