2017-07-20 118 views
10

Verilen kod:Linq işlevleri, IEnumerable'ın belirsiz kullanımı durumunda olası garip derleme hatası verir - olası geçici çözümler? (Gerçek kullanım durumunda uygulamaları ile) aşağıdakine benzer

class Animal 
{ 
    public bool IsHungry { get; } 
    public void Feed() { } 
} 
class Dog : Animal 
{ 
    public void Bark() { } 
} 

class AnimalGroup : IEnumerable<Animal> 
{ 
    public IEnumerator<Animal> GetEnumerator() { throw new NotImplementedException(); } 
    IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } 
} 

class AnimalGroup<T> : AnimalGroup, IEnumerable<T> 
    where T : Animal 
{ 
    public new IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); } 
} 

Her şey düz bir foreach ... mesela ile harika çalışıyor Aşağıdaki ince derler:

var animals = new AnimalGroup(); 
var dogs = new AnimalGroup<Dog>(); 

    // feed all the animals 
    foreach (var animal in animals) 
    animal.Feed(); 

    // make all the dogs bark 
    foreach (var dog in dogs) 
    dog.Bark(); 

Biz de tüm aç hayvanları beslemek için kod derlemek olabilir:

// feed all the hungry animals 
    foreach (var animal in animals.Where(a => a.IsHungry)) 
    animal.Feed(); 

... ama benzer kod değişiklikleri kullanmaya çalışırsanız sadece aç köpekler kabuğunu yapmak biz kullanılabilecek bir uzantısı yöntemi yoktur aslında gibi bu çok garip bir hata gibi görünen bir derleme hatayı

// make all the hungry dogs bark 
    foreach (var dog in dogs.Where(d => d.IsHungry)) 
    dog.Bark(); 
    // error CS1061: 'AnimalGroup<Dog>' does not contain a definition for 'Where' and 
    // no extension method 'Where' accepting a first argument of type 'AnimalGroup<Dog>' 
    // could be found (are you missing a using directive or an assembly reference?) 

olsun. Derleyicinin, bunun için hangi genel parametrenin kullanılacağını belirlediği ve belirsiz genel parametreler için en iyi eşleme uzantısı işlevine ilişkin yeterli bir hata mesajının bulunmadığını düşünür.

yerine, ben bir arayüz olmadan AnimalGroup<T> tanımlayacak: (foreach arabirimi yoktur olsa bile GetEnumerator işlevini kullanır çünkü)

class AnimalGroup<T> : AnimalGroup 
    where T : Animal 
{ 
    public new IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); } 
} 

ilk 3 test durumları hala çalışır. 4. vakadaki hata mesajı daha sonra bir hayvan yapmaya çalışan bir köpeğe gider (bu bir köpek olur, fakat tip sistem KNOW bir köpekdir). Bu, foreach döngüsünde var'un Dog'a değiştirilmesiyle düzeltilebilir (ve tüm köpekler numaralandırıcıdan geri döndürülürse, dog?.Bark() kullanarak tamlık için). benim gerçek kullanım durumunda

, ben çok daha muhtemeldir AnimalGroup den AnimalGroup<T> başa isteyen edilecek (ve aslında IReadOnlyList<T> yerine IEnumerable<T> kullanıyor) değilim. 2 ve 4 numaralı durumlarda olduğu gibi kod yapılması beklendiği gibi Linq işlevlerinin doğrudan AnimalGroup (tamlık için de istenebilir, ancak çok daha düşük öncelikli) olarak adlandırılmasına izin vermekten çok daha yüksek bir önceliktir. Bu yüzden, bir arabirim olmadan AnimalGroup yeniden tanımlanarak Bu: Bu üçüncü durumda hatayı taşır

class AnimalGroup 
{ 
    public IEnumerator<Animal> GetEnumerator() { throw new NotImplementedException(); } 
} 
class AnimalGroup<T> : AnimalGroup, IEnumerable<T> 
    where T : Animal 
{ 
    public new IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); } 
    IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } 
} 

"tüm aç hayvanları beslemek" - Ben ile yaşayabilir, (ve hata mesajı bu bağlamda mantıklı gerçekten hiçbir geçerli uzatma yöntemi yoktur) şimdi. (Şimdi düşünüyorum, temel sınıfta jenerik olmayan bir IEnumerable arabirim bırakmış olabilir, ancak bu Linq işlevleri sadece genel arabirimde çalışır, foreach gerektirmez ve IEnumerable kullanarak arayanlar gibi, hiçbir yararı yoktur sonucu object'dan göndermeliyim.

Ben ikinci 2 doğrudan bir başka yerine (Enumerable.Where çağırarak beklendiği gibi bu test durumları Tüm 4 derlemek, böylece AnimalGroup yeniden tanımlamak ve/veya AnimalGroup<T> ki böyle Henüz düşünce ettik bir yolu var mı Where Fonksiyonu tanımlarım)?

İlginç bir sınır da var genericAnimals = new AnimalGroup<Animal>;. Farklı bir tür parametresiyle derleme yapmayan aynı sınıf olsa bile, genericAnimals.Where(...)'u beklendiği gibi kullanır.Dediğiniz gibi

class AnimalGroup<T> : IEnumerable<T> 
    where T : Animal 
{ 
    public IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); } 
    IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } 
} 

class AnimalGroup : AnimalGroup<Animal> { } 
+0

AnimalGroup neden jenerik olmayan AnimalGroup'dan miras alınır? – pkuderov

+0

@pkuderov - Bunun iyi bir neden olduğunu düşündüm, ama onları değiştirmek hiç sorun yaratmıyor gibi görünüyor, bu yüzden kötü bir sebep olmalı! – Steve

cevap

3

System.Linq yönergesi). Sorun, derleyicinin Enumerable.Where için tür argümanını çıkaramamasıdır, çünkü AnimalGroup<Dog> hem IEnumerable<Animal> hem de IEnumerable<Dog>'u uygular. Belirtin doğrudan tip argüman

  • AnimalGroup<Dog>
  • yerine IEnumerable<Dog> olarak dogs bildirin::

    foreach (var dog in dogs.Where<Dog>(d => d.IsHungry)) 
    

Sen AnimalGroup yapabiliriz AnimalGroup değişen içermeyen

Seçenekler jenerik olmayanları uygulamak IEnumerable - genel olmayan arabirim için Where yöntemi olmadığı için derleyiciyi karıştırmaz. (Böylece tip çıkarımlar kullanmayan arasında seçim gerekir - bir tür IEnumerable<Foo> ve IEnumerable<Bar> Eğer tür kesmesi ile ilgili sorunlar yaşayacaksın uygular Temelde zaman

foreach (var animal in animals.Cast<Animal>().Where(a => a.IsHungry)) 

: Daha sonra yine Cast kullanarak üçüncü kullanım örneğini uygulamak Where için kolay - diğer durumlarda daha sert) veya her iki arabirimi de uygulamaz.

+0

Muhtemelen bu soruna neden olan genel miras durumlarında geçerli olmamakla birlikte, kesinlikle benim sorunumun cevabı olarak çalışmış gibi görünüyordu. Onu nasıl özledim bilmiyorum. Teşekkürler! – Steve

9

, hata mesajı bu sorun Where tüm yeralmaktadır değil ziyade belirsizlik olduğunu talihsiz (varsayarak var: jenerik AnimalGroup<T> olmayan jenerik AnimalGroup uygulamayla ilgili neler

+0

Mesajla ilgili öneriler ve ek bilgiler için teşekkürler. Ancak, sınıf bildiriminde çalışan bir çözüm aramaya çalışıyorum, böylece arayanların bu türden çemberler üzerinden atlamaya ihtiyaçları yoktur. Bu test durumlarının 4'ünün hatasız derlenmesini sağlarsa (AnimalGroup ve AnimalGroup ) tanımları için gerekli değişiklikleri (neden içinde!) Yapmaktan dolayı mutluyum. serileştirmeyi bozun, ancak bu soru için kapsam dışında bir sorun var!) – Steve

+0

@Steve: Kendi * örneğinizi * "Nerede" vb. eklemediğiniz sürece sorunlu olacağını düşünüyorum. Temel olarak, bir sınıf uygulamasına sahip olmak IEnumerable 'iki farklı T türü için sorunlara neden olur. Bu mirası kullanmak için * sizde * var mı? AnimalGroup ''dan, bundan devralmak yerine bir' AnimalGroup' içerebilir misiniz? –

+0

Tekrar teşekkürler, ama @pkuderov beni doğru yola koydu (kaçırdığım açık bir seçenek). Mirasın tersi bir şekilde (jenerikden jenerik olmayan kalıtım yoluyla) devralınması, gerçek kullanım durumunda derlenir ve iki ayrı "IEnumerable " implementasyonu olmasını önler. – Steve