C # - Không thể chuyển đổi hoàn toàn loại Danh sách <Sản phẩm> thành Danh sách <Sản phẩm>


89

Tôi có một dự án với tất cả các định nghĩa về Giao diện của mình: RivWorks. Giao diện
Tôi có một dự án mà tôi xác định việc cấy ghép cụ thể: RivWorks.DTO

Tôi đã làm điều này hàng trăm lần trước đây nhưng vì một số lý do mà tôi gặp lỗi này bây giờ:

Không thể chuyển đổi hoàn toàn kiểu 'System.Collections.Generic.List <RivWorks.DTO.Product>' thành 'System.Collections.Generic.List <RivWorks.Interfaces.DataContracts.IProduct>'

Định nghĩa giao diện (rút gọn):

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

Định nghĩa lớp bê tông (rút gọn):

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

Trong một lớp học khác, tôi có:

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

Bất kỳ ý tưởng tại sao điều này có thể xảy ra? (Và tôi chắc chắn rằng nó là một cái gì đó đơn giản tầm thường!)

Câu trả lời:


113

Đúng, đó là một giới hạn hiệp phương sai trong C #. Bạn không thể chuyển đổi danh sách loại này sang danh sách loại khác.

Thay vì:

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

Bạn phải làm điều này

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

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

Eric Lippert giải thích lý do họ triển khai nó theo cách này: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

(Và tại sao nó lại khác so với làm việc với các mảng các mục).


Cả Daniel và Kevin đều trả lời điều này, mặc dù theo những cách khác nhau. Hoan hô! Giải thích hợp lý về tương phản / hiệp phương sai. Nó thực sự là IN hoặc OUT nhưng không phải cả hai. Đã không sử dụng .NET 4.0 mà suy nghĩ của tôi hoàn toàn! Cảm ơn các bạn.
Keith Barrows

39

Bạn không thể làm điều đó. Nếu bạn có List<IProduct>, bạn có thể đặt bất kỳ IProduct vào đó. Vì vậy, nếu bạn có một nông cụ Product2nào đó, IProductbạn có thể đưa nó vào danh sách. Nhưng danh sách ban đầu được tạo ra List<Product>, vì vậy bất kỳ ai sử dụng danh sách sẽ chỉ mong đợi các đối tượng cùng loại Product, không Product2có trong danh sách.

Trong .NET 4.0, họ đã thêm hiệp phương sai và tương phản cho các giao diện, vì vậy bạn có thể chuyển đổi IEnumerable<Product>sang IEnumerable<IProduct>. Nhưng điều này vẫn không hoạt động đối với danh sách, vì giao diện danh sách cho phép bạn vừa "đưa đồ vào" và "lấy đồ ra".


8
Đẹp tip về việc có thể sử dụng IEnumerable
Lee Englestone

Tôi vẫn còn hơi bối rối bởi phần này:> vì vậy bất kỳ ai sử dụng danh sách sẽ chỉ mong đợi các đối tượng thuộc loại Sản phẩm, không phải Sản phẩm2 có trong danh sách. Tại sao người dùng lại đưa ra giả định đó? Nếu tôi đang sử dụng thứ gì đó được khai báo là Danh sách <Sản phẩm>, tôi sẽ không mong đợi có thể tự động downcast các phần tử xuống triển khai này hay cách khác. (Có thể đây chỉ là một lựa chọn kỳ quặc mà tôi không thích bởi MSFT, nhưng nếu không, tôi thực sự muốn cố gắng hiểu lý do của họ.)
Eleanor Holley

5

Cũng như một nhận xét: Covariance và Contravariance trong Generics đã được thêm vào trong C # 4.0.


1
Nhưng không được hỗ trợ trong các cấu trúc có cả kênh IN và OUT. Danh sách <T> là một cấu trúc như vậy nên chúng không hỗ trợ đối phương / hiệp phương sai!
Keith Barrows

Có, nó sẽ chỉ hoạt động nếu sử dụng IEnumerable <contract.IProduct>
Danvil

4

Chà, bạn có thể sử dụng cái này!

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

Bây giờ, để đưa ra giải pháp trực tiếp,

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

Đây là một ví dụ nhỏ về cách thực hiện.

    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

Hãy thử cái này thay thế:

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

7
nghiêm túc? bạn có biết điều đó có nghĩa là gì không?
Nix
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.