C #-不能隐式地将类型 List < Product > 转换为 List < IProduct >

我有一个包含所有接口定义的项目: 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>();
...

知道为什么会发生这种情况吗? (我肯定这是一件非常简单的事情!)

87505 次浏览

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 (archived)

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

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".

Try this instead:

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

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

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

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