Чому ключове слово 'out' використовується у двох, здавалося б, неоднакових контекстах?


11

У C # outключове слово можна використовувати двома різними способами.

  1. Як модифікатор параметра, в якому аргумент передається за посиланням

    class OutExample
    {
        static void Method(out int i)
        {
            i = 44;
        }
        static void Main()
        {
            int value;
            Method(out value);
            // value is now 44
        }
    }
  2. Як модифікатор параметрів типу для визначення коваріації .

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

Моє запитання: чому?

Початківцю зв’язок між ними не здається інтуїтивно зрозумілим . Використання з дженериками, схоже, не має нічого спільного з проходженням посилання.

Я вперше дізнався, що outстосується передачі аргументів шляхом посилання, і це перешкоджало моєму розумінню використання визначення визначальної коваріації з дженериками.

Чи існує зв’язок між цими напрямами, які мені не вистачає?


5
З'єднання трохи зрозуміліше, якщо ви подивитесь на використання коваріації та противаріантності в System.Func<in T, out TResult>делегаті .
rwong

4
Крім того, більшість мовних дизайнерів намагаються мінімізувати кількість ключових слів, і додавання нового ключового слова в якусь існуючу мову з великою кодовою базою є болісним (можливий конфлікт із деяким наявним кодом, використовуючи це слово як ім'я)
Базиль Старинкевич

Відповіді:


20

Зв'язок є, проте він трохи розпущений. У C # ключові слова «in» і «out», як їх назва пропонує підставку для введення та виведення. Що стосується вихідних параметрів, це дуже зрозуміло, але менш чисто, що це стосується параметрів шаблону.

Давайте подивимось на принцип заміни Ліскова :

...

Принцип Ліскова накладає деякі стандартні вимоги до підписів, прийнятих у нових об'єктно-орієнтованих мовах програмування (як правило, на рівні класів, а не типів; для розрізнення див. Номінальний та структурний підтипи):

  • Протилежність аргументів методу в підтипі.
  • Коваріація повернених типів у підтипі.

...

Подивіться, як контраваріантність пов'язана з входом, а коваріація пов'язана з результатом? У C #, якщо ви позначите змінну шаблону, outщоб зробити його коваріантною, але, зауважте, ви можете це зробити лише в тому випадку, якщо згаданий параметр типу відображається лише як вихід (тип повернення функції). Отже, таке недійсне:

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.

}

Подібне, якщо ви позначите параметр типу з in, це означає, що ви можете використовувати його лише як вхідний (функціональний параметр). Отже, таке недійсне:

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

}

Отже, підсумовуючи, зв'язок із outключовим словом полягає в тому, що з параметрами функції це означає, що це вихідний параметр, а для параметрів типу це означає, що тип використовується лише у контексті виводу .

System.Funcтакож є хорошим прикладом того, що згадував rwong у своєму коментарі. У System.Funcвсіх вхідних параметрах ar позначено знаком in, а вихідний параметр позначений фланцем out. Причина - саме те, що я описав.


2
Гарна відповідь! Врятувало мене дещо… чекай… набравши! До речі: частина ЛСП, яку ви цитували, насправді була відома задовго до Ліскова. Це лише стандартні правила підтипу для типів функцій. (Типи параметрів є протилежними, типи повернення - коваріантними). Новизна підходу Ліськова полягала в тому, що: «формулювання правил не з точки зору спів / протиріччя, а з точки зору заміни поведінки (як визначено попередніми / пост-умовами) та б) правила історії , що дозволяє застосовувати все це міркування до змінних типів даних, що раніше було неможливо.
Йорг W Міттаг

10

@ Gábor вже пояснив зв'язок (протиріччя для всього, що йде "в", коваріація для всього, що виходить "назовні"), але навіщо взагалі повторно використовувати ключові слова?

Що ж, ключові слова дуже дорогі. Ви не можете використовувати їх як ідентифікатори у своїх програмах. Але в англійській мові є лише стільки слів. Отже, іноді ви стикаєтеся з конфліктами, і вам доведеться незграбно перейменувати свої змінні, методи, поля, властивості, класи, інтерфейси чи структури, щоб уникнути зіткнення з ключовим словом. Наприклад, якщо ви моделюєте школу, як ви називаєте клас? Ви не можете назвати це класом, тому що classце ключове слово!

Додавання ключового слова на мову ще більш дорогим. Це, в основному, робить увесь код, який використовує це ключове слово як ідентифікатор, незаконним, порушуючи його сумісність у всьому світі.

inІ outключові слова вже існує, так що вони могли просто бути використані повторно.

Вони могли б додати контекстні ключові слова, які є лише ключовими словами в контексті списку параметрів типу, але які ключові слова вони обрали б? covariantі contravariant? +і -(як Скала, наприклад)? superі extendsяк робила Java? Чи можете ви пригадати вгорі голови, які параметри коваріантні та противаріантні?

У поточному рішенні є хороший мнемонічний: параметри типу виводу отримують outключове слово, параметри типу введення отримують inключове слово. Зверніть увагу на приємну симетрію з параметрами методу: вихідні параметри отримують outключове слово, вхідні параметри отримують inключове слово (ну, насправді, зовсім немає ключового слова, оскільки введення за замовчуванням, але ви отримаєте ідею).

[Примітка: якщо ви подивитесь на історію редагування, ви побачите, що я фактично спочатку переключив їх у своєму вступному реченні. І я навіть отримав нагороду за цей час! Це якраз і показує вам, наскільки важливо насправді мнемоніка.]


Спосіб запам'ятовування контр-дисперсії - це розглянути, що відбувається, якщо функція в інтерфейсі приймає параметр загального типу інтерфейсу. Якщо у нього є interface Accepter<in T> { void Accept(T it);};, а Accepter<Foo<T>>, приймається Tяк вхідний параметр, якщо Foo<T>він приймає його як вихідний параметр, і навпаки. Таким чином, проти -variance. Навпаки, interface ISupplier<out T> { T get();};у Supplier<Foo<T>>волі буде будь-яка різновид дисперсії Foo- таким чином, спів- варіантність.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.