Делегат System.Action не принимает 0 аргументов. Это ошибка компилятора C # (лямбда + два проекта)? - PullRequest
42 голосов
/ 17 декабря 2010

Рассмотрим код ниже.Похоже, совершенно правильный код C #, верно?

//Project B
using System;
public delegate void ActionSurrogate(Action addEvent);
//public delegate void ActionSurrogate2();
// Using ActionSurrogate2 instead of System.Action results in the same error
// Using a dummy parameter (Action<double, int>) results in the same error

// Project A
public static class Class1 {
    public static void ThisWontCompile() {
        ActionSurrogate b = (a) =>
                            {
                                a(); // Error given here
                            };
    }
}

Я получаю сообщение об ошибке компилятора «Делегат« Действие »не принимает 0 аргументов».в указанной позиции с помощью компилятора (Microsoft) C # 4.0.Обратите внимание, что вы должны объявить ActionSurrogate в другом проекте, чтобы эта ошибка проявилась.

Это становится более интересным:

// Project A, File 1
public static class Class1 {
    public static void ThisWontCompile() {
        ActionSurrogate b = (a) => { a(); /* Error given here */ };
        ActionSurrogate c = (a) => { a(); /* Error given here too */ };
        Action d = () => { };
        ActionSurrogate c = (a) => { a(); /* No error is given here */ };
    }
}

Я наткнулся здесь на ошибку компилятора C #?

Обратите внимание, что это довольно неприятная ошибка для тех, кто очень любит использовать лямбды и пытается создать библиотеку структур данных для будущего использования ... (я)

РЕДАКТИРОВАТЬ: удален ошибочный регистр.

Я скопировал и сократил свой первоначальный проект до минимума, чтобы это произошло.Это буквально весь код моего нового проекта.

Ответы [ 3 ]

67 голосов
/ 17 декабря 2010

ЗАКЛЮЧИТЕЛЬНОЕ ОБНОВЛЕНИЕ:

Ошибка была исправлена ​​в C # 5. Снова извиняюсь за неудобства, и спасибо за отчет.


Исходный анализ:

Я могу воспроизвести проблему с помощью компилятора командной строки.Это конечно похоже на ошибку.Это, наверное, моя вина;Извини за это.(Я написал весь код проверки преобразования лямбда-в-делегате.)

Я сейчас в кофейне и отсюда у меня нет доступа к источникам компилятора.Я постараюсь найти время, чтобы воспроизвести это в отладочной сборке завтра и посмотреть, смогу ли я решить, что происходит.Если я не найду время, я буду вне офиса до Рождества.

Ваше наблюдение, что введение переменной типа Action приводит к исчезновению проблемы, чрезвычайно интересно.Компилятор поддерживает множество кэшей как по соображениям производительности, так и для анализа, требуемого спецификацией языка.В частности, у лямбд и локальных переменных много сложной логики кэширования.Я был бы готов поспорить на доллар, что какой-то кеш здесь инициализируется или вводится неправильно, и что использование локальной переменной заполняет правильное значение в кеше.

Спасибо задоклад!

ОБНОВЛЕНИЕ: Я сейчас в автобусе, и он только что пришел ко мне;Я думаю, что я точно знаю, что не так.Компилятор ленивый , особенно когда речь идет о типах, полученных из метаданных.Причина в том, что в ссылочных сборках могут быть сотни тысяч типов, и нет необходимости загружать информацию обо всех из них.Вероятно, вы собираетесь использовать их менее чем на 1%, поэтому давайте не будем тратить много времени и средств на загрузку памяти, которые вы никогда не собираетесь использовать.На самом деле лень идет глубже, чем это;тип проходит через несколько «стадий», прежде чем его можно будет использовать.Сначала известно его имя, затем его базовый тип, затем является ли его иерархия базового типа обоснованной (ациклический и т. Д.), Затем его ограничения параметров типа, затем его члены, а затем, являются ли члены обоснованными (что переопределяет что-то, переопределяетс той же сигнатурой и т. д.) Держу пари, что логике преобразования не удается вызвать метод, который говорит «убедитесь, что типы всех параметров делегата имеют их известные члены», прежде чемпроверяет подпись делегата вызова на совместимость.Но код, который делает локальную переменную, вероятно, делает .Я думаю, что во время проверки преобразования тип Action может даже не иметь метода invoke, поскольку это касается компилятора.

Мы скоро выясним.

ОБНОВЛЕНИЕ: Мои психические способностисильны этим утром.Когда разрешение перегрузки пытается определить, существует ли метод Invoke типа делегата, который принимает нулевые аргументы, он находит нулевых методов Invoke на выбор .Мы должны убедиться, что метаданные типа делегата полностью загружены, прежде чем мы сделаем разрешение перегрузки.Как странно, что это осталось незамеченным так долго;это репродукция в C # 3.0.Конечно, он не воспроизводится в C # 2.0 просто потому, что не было лямбд;анонимные методы в C # 2.0 требуют явного указания типа, который создает локальный, который, как мы знаем, загружает метаданные.Но я бы предположил, что основная причина ошибки - то, что разрешение перегрузки не заставляет загружать метаданные для вызова - восходит к C # 1.0.

В любом случае, захватывающая ошибка, спасибо за отчет.Очевидно, у вас есть обходной путь.Я прослежу, чтобы QA отследил его отсюда, и мы постараемся исправить его для C # 5. (Мы пропустили окно для Service Pack 1, который уже находится в бета-версии .)

23 голосов
/ 17 декабря 2010

Это, вероятно, проблема с типом infrance, в некоторых случаях компилятор выводит a как Action<T> вместо Action (может показаться, что a равно ActionSurrogate, что соответствует сигнатуре Action<Action>>) , Попробуйте указать тип a явно:

    ActionSurrogate b = (Action a) =>
                        {
                            a();
                        };

Если это не так - возможно, проверьте ваш проект на наличие каких-либо самоопределенных Action делегатов, принимающих один параметр.

2 голосов
/ 17 декабря 2010
    public static void ThisWontCompile()
        {
            ActionSurrogate b = (Action a) =>
            {
                a();
            };


        }

Это скомпилируется.Некоторые проблемы с компилятором: он не может найти делегат Action без параметров.Вот почему вы получаете ошибку.

public delegate void Action();
public delegate void Action<T>();
public delegate void Action<T1,T2>();
public delegate void Action<T1,T2,T3>();
public delegate void Action<T1,T2,T3,T4>();
...