Питання Як додавання перерви у циклі while вирішує неоднозначність перевантаження?


Розгляньте цей фрагмент реактивних розширень (ігноруйте його практичність):

return Observable.Create<string>(async observable =>
{
    while (true)
    {
    }
});

Це не складається з Reactive Extensions 2.2.5 (використовуючи NuGet Rx-Main пакет). Це не вдається:

Помилка 1 Виклик неоднозначний між наступними методами або властивостями: 'System.Reactive.Linq.Observable.Create <string> (System.Func <System.IOBServer <string>, System.Threading.Tasks.Task <System.Action> >) "і" System.Reactive.Linq.Observable.Create <string> (System.Func <System.IOBServer <string>, System.Threading.Tasks.Task>) "

Однак додавання а break де-небудь у циклі while виправляє помилку компіляції:

return Observable.Create<string>(async observable =>
{
    while (true)
    {
        break;
    }
});

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

class Program
{
    static void Main(string[] args)
    {
        Observable.Create<string>(async blah =>
        {
            while (true)
            {
                Console.WriteLine("foo.");
                break; //Remove this and the compiler will break
            }
        });
    }
}

public class Observable
{
    public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, Task> subscribeAsync)
    {
        throw new Exception("Impl not important.");
    }

    public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, Task<Action>> subscribeAsync)
    {
        throw new Exception("Impl not important.");
    }
}

public interface IObserver<T>
{
}

Ігнорування частини реактивних розширень, чому додавання break допоможе компілятор C # вирішити неоднозначність? Як це можна описати з правилами розподілу перевантажень із специфікації C #?

Я використовую Visual Studio 2013 оновлення 2 орієнтації 4.5.1.


48
2017-09-17 19:12


походження


@ Mic1780: звідки коли компілятори виходять з ладу при виявленні нескінченного циклу? Ніколи не бачили цього раніше ... - knittl
@ Mic1780 Не знаю, про що ти говориш. Компілятор C # (насправді, більшість компіляторів) з радістю скомпілювати нескінченний цикл, навіть один так просто, як while(true) {} - tnw


Відповіді:


Найлегше просто витягнути async як і лямбдас, оскільки він підкреслює, що відбувається. Обидва ці методи є дійсними і будуть компілювати:

public static void Foo()
{
    while (true) { }
}
public static Action Foo()
{
    while (true) { }
}

Однак для цих двох методів:

public static void Foo()
{
    while (true) { break; }
}
public static Action Foo()
{
    while (true) { break; }
}

Перший компілює, а другий - не. Він має кодовий шлях, який не повертає дійсного значення.

Насправді, while(true){} (разом з throw new Exception();) є цікавим твердженням, що це дійсний орган методу з будь-яким типом повернення.

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

Звичайно, принести async Повернутися до гри, це насправді актуально в одному сенсі тут. Для async методи, які вони обидва завжди повертають щось, будь то а Task або a Task<T>. Алгоритми "goodness" для дозволу перевантаження віддадуть перевагу делегатам, які повертають значення void делегати, коли є лямбда, яка може поєднуватись, однак у вашому випадку обидва перевантаження мають делегатів, які повертають значення, той факт, що для async методи повернення а Task замість а Task<T> це концептуальний еквівалент не повернення значення, не включене в цей алгоритм стабільності. Через це неасинхронний еквівалент не призведе до помилки неоднозначності, навіть якщо обидва перевантаження застосовні.

Звичайно, варто зазначити, що написання програми для визначення того, чи довільний блоковий код коли-небудь закінчиться, є значною мірою нерозв'язною проблемою, однак, хоча компілятор не може правильно оцінити, чи виконуватиметься кожен окремий фрагмент, в деяких простих випадках це може довести як цей, кодекс насправді ніколи не закінчиться. Через це існують способи написання коду, який ясно (для вас і мене) ніколи не завершуватиметься, але що компілятор розглядатиме як можливо завершений.


59
2017-09-17 19:19



Там щось конкретно async хоча: дано void Foo(Action a); void Foo(Func f);, Foo(() => { throw new Exception(); }); } є ні неоднозначно і буде викликати другу перевантаження: специфікація мови сприяє делегуванню типу повернення. Але async функції можуть мати тип повернення, навіть якщо вони насправді не повертають конкретну цінність: воно все ще може мати Task повернути тип - hvd
@hvd Right, додав у пункті, щоб перейти до цього. - Servy
Цікаво. Так while(this.PropertyIsAlwaysTrue) {} (або MethodReturnsTrue()) знову вирішить метод із фіксованим поверненням значення (оскільки компілятор не може знати, що повернеться властивість). - knittl
@knittl Компілятор C # лише одночасно здійснює семантичний аналіз одного блоку одночасно. Він ніколи не розгляне джерело декількох різних методів / властивостей, щоб визначити, як скомпілювати один з них. Теоретично, це може означати, що це дуже важко, дуже швидко, тому було прийнято рішення, що не варто витрачати на цей рівень зусилля за те, що ви отримуєте в обмін. - Servy


Залишаючи async з цього слід починати з ...

З перервою досягає кінця лямбда-виразу, отже, повернення типу лямбда мав бути void.

Без перерви кінець лямбда-виразу недоступний, так що будь-який Тип повернення буде дійсним. Наприклад, це добре:

Func<string> foo = () => { while(true); };

в той час як це не так:

Func<string> foo = () => { while(true) { break; } };

Так без цього break, вираз лямбда може бути конвертованим до будь-якого типу делегатів з одним параметром. З break, лямбда-вираз є тільки конвертований у тип делегата з єдиним параметром і тип повернення void.

Додати async частина і void стає void або Task, проти void, Task або Task<T> для будь-якого T де раніше ви мали будь-який тип повернення. Наприклад:

// Valid
Func<Task<string>> foo = async () => { while(true); };
// Invalid (it doesn't actually return a string)
Func<Task<string>> foo = async () => { while(true) { break; } };
// Valid
Func<Task> foo = async () => { while(true) { break; } };
// Valid
Action foo = async () => { while(true) { break; } };

25
2017-09-17 19:21