C # - ไม่สามารถแปลงประเภทรายการ <Product> เป็นรายการ <IProduct> โดยปริยาย


89

ฉันมีโปรเจ็กต์ที่มีคำจำกัดความอินเทอร์เฟซทั้งหมดของฉัน: RivWorks อินเตอร์เฟส
ฉันมีโปรเจ็กต์ที่ฉันกำหนดการฝังตัวที่เป็นรูปธรรม: 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());

Eric Lippert อธิบายว่าเหตุใดจึงใช้วิธีนี้: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

(และเหตุใดจึงแตกต่างจากการทำงานกับอาร์เรย์ของรายการ)


ทั้งดาเนียลและเควินตอบคำถามนี้แม้ว่าจะต่างกันก็ตาม ไชโย! คำอธิบายที่เป็นธรรมของการตรงกันข้าม / ความแปรปรวนร่วม แน่นอนว่าเป็นทั้ง IN หรือ OUT แต่ไม่ใช่ทั้งสองอย่าง การที่ไม่ได้ใช้. NET 4.0 นั้นทำให้จิตใจของฉันแย่ลงอย่างสิ้นเชิง! ขอบคุณเพื่อน.
Keith Barrows

39

คุณไม่สามารถทำเช่นนั้นได้ หากคุณมีList<IProduct>คุณสามารถใส่ใด ๆ IProductอยู่ในนั้น ดังนั้นหากคุณมีอุปกรณ์Product2ใดที่IProductคุณสามารถใส่ไว้ในรายการได้ แต่รายการดั้งเดิมถูกสร้างขึ้นเป็นList<Product>ดังนั้นทุกคนที่ใช้รายการจะคาดหวังเฉพาะออบเจ็กต์ประเภทProductไม่Product2ให้อยู่ในรายการ

ใน. NET 4.0 พวกเขาได้เพิ่มความแปรปรวนร่วมและความแตกต่างสำหรับอินเทอร์เฟซดังนั้นคุณสามารถแปลงIEnumerable<Product>เป็นIEnumerable<IProduct>ไฟล์. แต่ยังคงใช้ไม่ได้กับรายการเนื่องจากอินเทอร์เฟซรายการช่วยให้คุณทั้งคู่ "ใส่ของเข้า" และ "เอาของออก" ได้


8
เคล็ดลับ
ดีๆ

ฉันยังคงสับสนเล็กน้อยในส่วนนี้:> ดังนั้นใครก็ตามที่ใช้รายการจะคาดหวังว่าจะมีเพียงออบเจ็กต์ประเภท Product เท่านั้นไม่ใช่ Product2 ที่จะอยู่ในรายการ เหตุใดผู้ใช้จึงตั้งสมมติฐานดังกล่าว หากฉันใช้บางสิ่งที่ประกาศเป็นรายการ <IProduct> ฉันจะไม่คาดหวังว่าจะสามารถดาวน์แคสต์องค์ประกอบไปยังการนำไปใช้งานหนึ่งหรืออย่างอื่นได้โดยอัตโนมัติ (เป็นไปได้ว่านี่เป็นเพียงทางเลือกที่แปลกประหลาดที่ฉันไม่ชอบโดย MSFT แต่ถ้าไม่ใช่ฉันอยากจะพยายามเข้าใจเหตุผลของพวกเขาจริงๆ)
Eleanor Holley

5

เช่นเดียวกับข้อสังเกต: ความแปรปรวนร่วมและความแตกต่างใน Genericsถูกเพิ่มใน C # 4.0


1
แต่ไม่รองรับโครงสร้างที่มีทั้งช่อง IN และ OUT รายการ <T> เป็นโครงสร้างดังนั้นจึงไม่สนับสนุนการตรงกันข้าม / ความแปรปรวนร่วม!
Keith Barrows

ใช่มันจะใช้ได้ก็ต่อเมื่อใช้ IEnumerable <contract.IProduct>
Danvil

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.