C # - Невозможно неявно преобразовать тип List <Product> в List <IProduct>

89

У меня есть проект со всеми моими определениями интерфейсов: RivWorks.Interfaces
У меня есть проект, в котором я определяю конкретные реализации: RivWorks.DTO

Я делал это сотни раз раньше, но по какой-то причине теперь получаю эту ошибку:

Невозможно неявно преобразовать тип System.Collections.Generic.List <RivWorks.DTO.Product> в System.Collections.Generic.List <RivWorks.Interfaces.DataContracts.IProduct>

Определение интерфейса (сокращенно):

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

Определение конкретного класса (сокращенно):

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

В другом классе у меня есть:

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

Есть идеи, почему это может происходить? (И я уверен, что это что-то банально простое!)

Кейт Бэрроуз
источник

Ответы:

114

Да, это ограничение ковариации в C #. Вы не можете преобразовать список одного типа в список другого.

Вместо того:

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

Ты должен сделать это

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

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

Эрик Липперт объясняет, почему они реализовали это таким образом: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

(И почему это отличается от работы с массивами элементов).

kemiller2002
источник
И Дэниел, и Кевин ответили на это, хотя и по-разному. Браво! Справедливое объяснение контра / ковариантности. Это действительно либо IN, либо OUT, но не оба одновременно. Я совершенно забыл о том, что не использовал .NET 4.0! Спасибо ребята.
Кейт Бэрроуз,
39

Вы не можете этого сделать. Если у вас есть List<IProduct>, вы можете положить в него любой IProduct . Так что, если у вас есть Product2какие-то орудия, IProductвы можете поместить их в список. Но исходный список был создан как List<Product>, поэтому любой, кто использует список, ожидал бы, что в списке будут только объекты типа Product, а не Product2.

В .NET 4.0 они добавили ковариацию и контравариантность для интерфейсов, чтобы вы могли преобразовать их IEnumerable<Product>в IEnumerable<IProduct>. Но это все еще не работает для списков, поскольку интерфейс списка позволяет вам как «вставлять», так и «извлекать».

Дэниел Плейстед
источник
8
Хороший совет о возможности использовать IEnumerable
Ли Энглстон,
Меня все еще немного смущает эта часть:> поэтому любой, кто использует список, будет ожидать, что в списке будут только объекты типа Product, а не Product2. Почему пользователь сделал такое предположение? Если я использую что-то, объявленное как List <IProduct>, я бы не ожидал, что автоматически смогу преобразовать элементы в ту или иную реализацию. (Возможно, это просто причудливый выбор MSFT, который мне не нравится, но если нет, я действительно хочу попытаться понять их аргументы.)
Элеонора Холли,
5

Просто замечание: ковариация и контравариантность в обобщениях были добавлены в C # 4.0.

Danvil
источник
1
Но не поддерживается в конструкциях, которые имеют каналы как IN, так и OUT. List <T> - такая конструкция, поэтому они не поддерживают контра / ковариацию!
Кейт Бэрроуз,
Да, это будет работать только при использовании IEnumerable <contract.IProduct>
Данвил,
4

Что ж, вы можете использовать это!

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

Теперь, чтобы дать прямое решение,

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());
}
Наян
источник
2

Это небольшой пример того, как это сделать.

    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.  ");
        }
    }
Джимми
источник
-3

Попробуйте вместо этого:

List<contracts.IProduct> myList = new List<contracts.IProduct>((new List<dto.Product>()).Cast<contracts.IProduct>());
Хмелевой
источник
7
шутки в сторону? ты знаешь, что это вообще значит?
Nix