C # 'da jeneriklerle kovaryans kontravansını anlama sorunu


115

Aşağıdaki C # kodunun neden derlenmediğini anlayamıyorum.

Gördüğünüz gibi, statik bir jenerik yöntemim var Bir IEnumerable<T>parametreye sahip bir şey (ve Tbir IAarabirim olarak sınırlandırılmıştır ) ve bu parametre örtük olarak IEnumerable<IA>.

Açıklaması nedir? (Neden işe yaramadığını anlamak için bir çözüm aramıyorum).

public interface IA { }
public interface IB : IA { }
public class CIA : IA { }
public class CIAD : CIA { }
public class CIB : IB { }
public class CIBD : CIB { }

public static class Test
{
    public static IList<T> Something<T>(IEnumerable<T> foo) where T : IA
    {
        var bar = foo.ToList();

        // All those calls are legal
        Something2(new List<IA>());
        Something2(new List<IB>());
        Something2(new List<CIA>());
        Something2(new List<CIAD>());
        Something2(new List<CIB>());
        Something2(new List<CIBD>());
        Something2(bar.Cast<IA>());

        // This call is illegal
        Something2(bar);

        return bar;
    }

    private static void Something2(IEnumerable<IA> foo)
    {
    }
}

Sıraya girdiğimde hata Something2(bar):

Bağımsız Değişken 1: 'System.Collections.Generic.List'den' System.Collections.Generic.IEnumerable'a dönüştürülemiyor



12
TReferans türleriyle sınırlandırmadınız . Koşulu kullanırsanız, where T: class, IAo zaman işe yaramalıdır. Bağlantılı yanıtta daha fazla ayrıntı var.
Dirk

2
@Dirk Bunun kopya olarak işaretlenmesi gerektiğini düşünmüyorum. Buradaki kavram probleminin, değer türleri karşısında bir kovaryans / kontravaryans problemi olduğu doğru olsa da, buradaki özel durum, "bu hata mesajının ne anlama geldiğini" ve yazarın sadece "sınıf" ın dahil edilmesinin farkına varmamasının sorununu çözdüğüdür. İnanıyorum ki gelecekteki kullanıcılar bu hata mesajını arayacak, bu gönderiyi bulacak ve mutlu ayrılacaktır. (Sık sık yaptığım gibi)
Reginald Blue

Something2(foo);Doğrudan söyleyerek de durumu yeniden oluşturabilirsiniz . Bunu anlamak .ToList()için bir List<T>( Tgenel yöntem tarafından bildirilen tür parametrenizdir) almak için dolaşmak gerekli değildir (a List<T>bir an'dır IEnumerable<T>).
Jeppe Stig Nielsen

@ReginaldBlue% 100, aynı şeyi gönderecekti. Benzer yanıtlar, yinelenen bir soru işareti değildir.
UuDdLrLrSs

Yanıtlar:


218

Hata mesajı yeterince bilgilendirici değil ve bu benim hatam. Bunun için üzgünüm.

Karşılaştığınız sorun, kovaryansın yalnızca referans türleri üzerinde işe yaradığı gerçeğinin bir sonucudur.

Şu anda muhtemelen "ama IAbir referans türü" diyorsunuzdur . Evet öyle. Ama T bunun eşit olduğunu söylemedin IA. Bunun uygulayanT bir tür olduğunu ve bir değer türünün bir arabirim uygulayabileceğini söylediniz . Bu nedenle kovaryansın işe yarayıp yaramayacağını bilmiyoruz ve buna izin vermeyiz. IA

Kovaryansın çalışmasını istiyorsanız, derleyiciye tip parametresinin hem classkısıtlı hem de IAarayüz kısıtlı bir başvuru tipi olduğunu söylemeniz gerekir .

Hata mesajı, dönüşümün gerçekten mümkün olmadığını söylemelidir, çünkü kovaryans, referans türü garantisini gerektirir, çünkü bu temel problemdir.


3
Neden senin hatan dedin?
user4951

77
@ user4951: Çünkü hata mesajları dahil tüm dönüştürme kontrol mantığını uyguladım.
Eric Lippert

@BurnsBA Bu sadece nedensel anlamda bir "hata" dır - teknik olarak uygulama ve hata mesajı tamamen doğrudur. (Sadece, dönüştürülemezliğin hata bildirimi gerçek nedenleri ayrıntılı olarak açıklayabilir. Ancak jeneriklerle iyi hatalar üretmek zordur - birkaç yıl önceki C ++ şablon hata mesajlarına kıyasla bu anlaşılır ve özlüdür.)
Peter - Monica'yı yeniden etkinleştirin

3
@ PeterA.Schneider: Bunu takdir ediyorum. Ancak Roslyn'de hata raporlama mantığını tasarlamak için birincil hedeflerimden biri, özellikle hangi kuralın ihlal edildiğini tespit etmek değil, ayrıca mümkün olduğunda "temel nedeni" belirlemekti. Örneğin, hata mesajı ne için olmalıdır customers.Select(c=>c.FristName)? C # belirtimi, bunun bir aşırı yük çözümleme hatası olduğu çok açıktır : Lambda'yı alabilen Select adlı uygulanabilir yöntemler kümesi boştur. Ancak temel neden, FirstNamebir yazım hatası olmasıdır.
Eric Lippert

3
@ PeterA.Schneider: Genel tip çıkarımını ve lambdaları içeren senaryoların, hangi hata mesajının geliştiriciye en iyi şekilde yardımcı olacağına karar vermek için uygun buluşsal yöntemleri kullandığından emin olmak için çok çalıştım. Ancak, özellikle varyans söz konusu olduğunda, dönüşüm hata mesajlarında çok daha az iyi bir iş çıkardım. Bundan hep pişman olmuşumdur.
Eric Lippert

26

Ben sadece Eric'in mükemmel içeriden öğrenen cevabını, genel kısıtlamalara aşina olmayanlar için bir kod örneği ile tamamlamak istedim.

Somethingİmzasını şu şekilde değiştirin : classKısıtlama önce gelmelidir .

public static IList<T> Something<T>(IEnumerable<T> foo) where T : class, IA

2
Merak ediyorum ... siparişin öneminin ardındaki neden tam olarak nedir?
Tom Wright

5
@TomWright - şartname elbette birçok "Neden?" Sorusunun yanıtını içermiyor. sorular, ancak bu durumda , üç farklı kısıtlama türü olduğunu ve üçü de kullanıldığında özel olarak olmaları gerektiğini açıkça ortaya primary_constraint ',' secondary_constraints ',' constructor_constraint
koyuyor

2
@TomWright: Damien haklı; ayrıştırıcının yazarının rahatlığından başka bildiğim özel bir neden yok. Eğer uyuşturuculara sahip olsaydım, tür kısıtlamaları için sözdizimi çok daha ayrıntılı olurdu . classkötüdür çünkü "sınıf" değil, "referans türü" anlamına gelir. Gibi ayrıntılı bir şeyle daha mutlu olurdumwhere T is not struct
Eric Lippert
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.