Neden 'out' anahtar kelimesi birbirinden farklı görünen iki bağlamda kullanılıyor?


11

C # 'da, outanahtar kelime iki farklı şekilde kullanılabilir.

  1. Bir bağımsız değişkenin başvuru ile iletildiği bir parametre değiştirici olarak

    class OutExample
    {
        static void Method(out int i)
        {
            i = 44;
        }
        static void Main()
        {
            int value;
            Method(out value);
            // value is now 44
        }
    }
    
  2. Kovaryans belirtmek için bir tür parametre değiştirici olarak .

    // Covariant interface. 
    interface ICovariant<out R> { }
    
    // Extending covariant interface. 
    interface IExtCovariant<out R> : ICovariant<R> { }
    
    // Implementing covariant interface. 
    class Sample<R> : ICovariant<R> { }
    
    class Program
    {
        static void Test()
        {
            ICovariant<Object> iobj = new Sample<Object>();
            ICovariant<String> istr = new Sample<String>();
    
            // You can assign istr to iobj because 
            // the ICovariant interface is covariant.
            iobj = istr;
        }
    }
    

Sorum şu: neden?

Yeni başlayanlar için, ikisi arasındaki bağlantı sezgisel görünmüyor . Jeneriklerle kullanımın referans ile geçme ile bir ilgisi yok gibi görünüyor.

İlk outolarak argümanları referansla geçme ile ilgili olanı öğrendim ve bu benim jeneriklerle kovaryans tanımlamanın kullanımını anlamamı engelledi.

Bu kullanımlar arasında eksik olduğum bir bağlantı var mı?


5
System.Func<in T, out TResult>Temsilcideki kovaryans ve karşıtlık kullanımına bakarsanız bağlantı biraz daha anlaşılır olur .
rwong

4
Ayrıca, çoğu dil tasarımcısı anahtar kelime sayısını en aza indirmeye çalışır ve büyük bir kod tabanına sahip mevcut bir dilde yeni bir anahtar kelime eklemek acı vericidir (bu sözcüğü ad olarak kullanan bazı mevcut kodlarla olası çakışma)
Basile Starynkevitch

Yanıtlar:


20

Bir bağlantı var, ancak biraz gevşek. C # 'da, adları olarak ´in´ ve ´out´ anahtar sözcükleri, girdi ve çıktı anlamına gelir. Bu, çıkış parametreleri durumunda çok açıktır, ancak şablon parametreleriyle ne yapacağını daha az temizler.

Liskov ikame ilkesine bir göz atalım :

...

Liskov ilkesi, daha yeni nesne yönelimli programlama dillerinde (genellikle türler yerine sınıf düzeyinde; imza için nominal ve yapısal alt tiplere bakınız) kabul edilen imzalara bazı standart gereklilikler getirir:

  • Alt türdeki yöntem bağımsız değişkenlerinin çelişkisi.
  • Alt tipte dönüş türlerinin kovaryansı.

...

Karşıtlığın girdi ile ve kovaryansın çıktı ile nasıl ilişkili olduğunu görüyor musunuz? C # 'da bir şablon değişkenini outkovaryant yapmak için işaretlerseniz , ancak bunu yalnızca belirtilen tür parametresi yalnızca çıktı (işlev dönüş türü) olarak görünüyorsa yapabileceğinizi unutmayın . Bu yüzden aşağıdakiler geçersiz:

interface I<out T>
{
  void func(T t); //Invalid variance: The type parameter 'T' must be
                  //contravariantly valid on 'I<T>.func(T)'.
                  //'T' is covariant.

}

Benzer şekilde in, bir type parametresini bayrakla işaretlerseniz , bunu yalnızca girdi (işlev parametresi) olarak kullanabileceğiniz anlamına gelir . Bu yüzden aşağıdakiler geçersiz:

interface I<in T>
{
  T func(); //Invalid variance: The type parameter 'T' must
            //be covariantly valid on 'I<T>.func()'. 
            //'T' is contravariant.

}

Özetlemek gerekirse, outanahtar kelimeyle bağlantı, işlev parametreleriyle bunun bir çıkış parametresi olduğu ve tür parametreleri için, türün yalnızca çıkış bağlamında kullanıldığı anlamına gelir .

System.Funcrwong'un yorumunda bahsettikleri iyi bir örnektir . Gelen System.Functüm giriş parametreleri ile flaged ar inve çıkış parametresi ile flaged edilir out. Nedeni tam olarak tarif ettiğim şey.


2
Güzel cevap! Beni kurtardım… bekleyin… yazarak! Bu arada: LSP'nin alıntıladığınız kısmı aslında Liskov'dan çok önce biliniyordu. Fonksiyon tipleri için sadece standart alt tipleme kurallarıdır. (Parametre tipleri kontravaryant, dönüş tipleri kovaryanttır). Liskov yaklaşımının yeniliği kurallarını phrasing) bir oldu değil eş / contravariance açısından ancak behavorial öncesi / Hedefşartlar tarafından tanımlanan ikame edilebilirlik () ve b) açısından geçmişi kural bütün bu muhakeme mümkün kılar, Daha önce mümkün olmayan değiştirilebilir veri tiplerine
Jörg W Mittag

10

@ Gábor bağlantıyı zaten açıkladı ("içeri giren her şey için aykırılık," dışarı "giden her şey için kovaryans), ama neden anahtar kelimeleri yeniden kullanmalıyım?

Anahtar kelimeler çok pahalıdır. Bunları programlarınızda tanımlayıcı olarak kullanamazsınız. Ancak İngilizce dilinde çok fazla kelime var. Bu nedenle, bazen çakışmalarla karşılaşırsınız ve bir anahtar kelimeyle çakışmasını önlemek için değişkenlerinizi, yöntemlerinizi, alanlarınızı, özelliklerinizi, sınıflarınızı, arayüzlerinizi veya yapılarınızı garip bir şekilde yeniden adlandırmanız gerekir. Örneğin, bir okulu modelleyiyorsanız, sınıfa ne dersiniz? Sınıf diyemezsiniz, çünkü classbir anahtar kelime!

Bir dile anahtar sözcük eklemek daha da pahalıdır. Temel olarak, bu anahtar kelimeyi bir tanımlayıcı olarak kullanan tüm kodu yasadışı hale getirir ve her yerde geriye dönük uyumluluğu bozar.

inVe outonlar sadece yeniden olabilir bu yüzden anahtar kelimeler zaten, var.

Onlar olabilir bir tür parametre listesinin kapsamında sadece anahtar kelimeler içeriksel anahtar kelimeler ekledik ama hangi anahtar kelimeler bunlar seçerdim? covariantve contravariant? +ve -(örneğin Scala'nın yaptığı gibi)? superve extendsJava'nın yaptığı gibi? Kafanızın üst kısmından hangi parametrelerin kovaryant ve kontravaryant olduğunu hatırlayabilir misiniz?

Mevcut çözüm ile hoş bir anımsatıcı var: çıkış tipi parametreleri outanahtar kelimeyi, giriş tipi parametreleri inanahtar kelimeyi alır. Yöntem parametreleriyle hoş simetriyi not edin: çıkış parametreleri outanahtar kelimeyi, giriş parametreleri inanahtar kelimeyi alır (giriş varsayılan olduğu için anahtar kelime yoktur, ancak fikri alırsınız).

[Not: Düzenleme geçmişine bakarsanız, aslında başlangıç ​​cümlesinde ikisini değiştirdiğimi göreceksiniz. Ve bu süre zarfında bir oylama bile aldım! Bu sadece anımsatmanın gerçekten ne kadar önemli olduğunu gösterecek.]


Karşı-varyansın tersini hatırlamanın yolu, bir arabirimdeki bir işlev genel bir arabirim türünün parametresini alırsa ne olacağını düşünmektir. Birinde bir interface Accepter<in T> { void Accept(T it);};, bir çıkış parametresi olarak kabul edilirse, bir giriş parametresi olarak Accepter<Foo<T>>kabul Tedilir Foo<T>ve bunun tersi de geçerlidir. Böylece, karşı- varyans. Aksine, interface ISupplier<out T> { T get();};bir Supplier<Foo<T>>tür varyansa Foosahip olacaktır - dolayısıyla ortak varyans.
supercat
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.