C # - Tidak dapat secara implisit mengonversi tipe List <Product> ke List <IProduct>

89

Saya memiliki proyek dengan semua definisi Antarmuka saya: RivWorks. Antarmuka
Saya memiliki proyek tempat saya mendefinisikan implementasi konkret: RivWorks.DTO

Saya telah melakukan ini ratusan kali sebelumnya tetapi untuk beberapa alasan saya mendapatkan kesalahan ini sekarang:

Tidak dapat secara implisit mengonversi jenis 'System.Collections.Generic.List <RivWorks.DTO.Product>' menjadi 'System.Collections.Generic.List <RivWorks.Interfaces.DataContracts.IProduct>'

Definisi antarmuka (disingkat):

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

Definisi kelas beton (disingkat):

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

Di kelas lain saya memiliki:

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

Adakah ide mengapa ini mungkin terjadi? (Dan saya yakin itu adalah sesuatu yang sangat sederhana!)

Keith Barrows
sumber

Jawaban:

114

Ya itu adalah batasan kovariansi di C #. Anda tidak dapat mengonversi daftar satu jenis menjadi daftar lain.

Dari pada:

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

Anda harus melakukan ini

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

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

Eric Lippert menjelaskan mengapa mereka menerapkannya dengan cara ini: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

(Dan mengapa ini berbeda dari bekerja dengan array item).

kemiller2002
sumber
Baik Daniel dan Kevin menjawab ini, meskipun dengan cara yang berbeda. Bravo! Penjelasan yang adil tentang kontra / kovarian. Ini memang MASUK atau KELUAR tetapi tidak keduanya. Belum pernah menggunakan .NET 4.0 yang benar-benar luput dari pikiran saya! Terima kasih teman-teman.
Keith Barrows
39

Kamu tidak boleh melakukan itu. Jika Anda memiliki List<IProduct>, Anda dapat menempatkan setiap IProduct di dalamnya. Jadi jika Anda memiliki Product2yang mengimplementasikan IProductAnda bisa memasukkannya ke dalam daftar. Tetapi daftar asli dibuat sebagai List<Product>, jadi siapa pun yang menggunakan daftar akan mengharapkan hanya objek bertipe Product, bukan Product2dalam daftar.

Dalam .NET 4.0, mereka menambahkan kovariansi dan kontradiksi untuk antarmuka, sehingga Anda dapat mengonversinya IEnumerable<Product>ke IEnumerable<IProduct>. Namun ini masih tidak berfungsi untuk daftar, karena antarmuka daftar memungkinkan Anda "memasukkan barang" dan "mengeluarkan barang".

Daniel Plaisted
sumber
8
Kiat bagus tentang dapat menggunakan IEnumerable
Lee Englestone
Saya masih sedikit bingung dengan bagian ini:> jadi siapa pun yang menggunakan daftar hanya akan mengharapkan objek bertipe Produk, bukan Product2 yang ada dalam daftar. Mengapa pengguna membuat asumsi itu? Jika saya menggunakan sesuatu yang dideklarasikan sebagai List <IProduct>, saya tidak akan berharap dapat secara otomatis menurunkan elemen ke satu implementasi atau lainnya. (Mungkin ini hanya pilihan unik yang tidak saya sukai oleh MSFT, tetapi jika tidak, saya benar-benar ingin mencoba memahami alasan mereka.)
Eleanor Holley
5

Hanya sebagai komentar: Kovarian dan Kontravarian dalam Generik ditambahkan di C # 4.0.

Danvil
sumber
1
Namun tidak didukung dalam konstruksi yang memiliki saluran IN dan OUT. Daftar <T> adalah konstruksi seperti itu sehingga tidak mendukung kontra / kovarian!
Keith Barrows
Ya, ini hanya akan berfungsi jika menggunakan IEnumerable <contracts.IProduct>
Danvil
4

Nah, kamu bisa menggunakan ini!

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

Sekarang, untuk memberikan solusi langsung,

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
sumber
2

Ini adalah contoh kecil bagaimana melakukannya.

    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
sumber
-3

Coba ini sebagai gantinya:

List<contracts.IProduct> myList = new List<contracts.IProduct>((new List<dto.Product>()).Cast<contracts.IProduct>());
Hoppy
sumber
7
serius? apakah kamu tahu apa artinya itu?
Nix