Почему я не могу использовать/бросать Действие для/к ThreadStart?

Оба - делегаты и имеют ту же подпись, но я не могу использовать Действие в качестве ThreadStart.

Почему?

Action doIt;
doIt = () => MyMethod("test");
Thread t;

t = new Thread(doIt);
t.Start();

но это, кажется, работает:

Thread t;

t = new Thread(() => MyMethod("test"));
t.Start();
9
задан Steven A. Lowe 10 January 2014 в 05:00
поделиться

8 ответов

Как отмечали другие, проблема в том, что типы делегатов не являются «структурными». То есть они не имеют эквивалентности по своей «структуре».

Это, возможно, хорошо для некоторых типов. Если у вас есть

struct MyRectangle { int x; int y; int width; int height; ... }

и

struct YourRectangle { int x1; int y1; int x2; int y2; ... } 

, очевидно, было бы ошибкой разрешать присваивать экземпляры MyRectangle переменным YourRectangle только потому, что они оба состоят из четырех целых чисел. Семантика int различается, и поэтому типы не эквивалентны.

Теоретически то же самое и с делегатами. У вас может быть

delegate int Pure(string x);
delegate int Func(string x);

, где «чистая» функция - это функция без побочных эффектов и тот же вывод при том же вводе. Поскольку каждый Pure логически является Func, но каждый Func не обязательно является Pure, между ними не должно быть структурной типизации.

На практике, конечно, система типов не очень хорошо поддерживает такие понятия, как «чистая функция». И на практике подавляющее большинство попыток преобразования между типами делегатов совершенно безопасны: преобразование из Func в Predicate и так далее.

Итак, две вещи: одна смотрит назад, другая - вперед. Обратно: если бы нам пришлось делать это снова и снова, я думаю, что делегаты, вероятно, были бы структурно типизированы в CLI. Вы не всегда знаете, какие функции будут полезны при разработке совершенно новой структуры, а неструктурные типы делегатов до сих пор оказались не такими полезными, как, возможно, ожидалось. Вперед: я ожидаю увидеть больше функций в будущих версиях CLR, которые позволят использовать более структурную типизацию. Функция «no pia» в C # 4, например, заключается в создании двух типов, которые семантически и структурно одинаковы, но определены в разных сборках, логически унифицированных структурно.

21
ответ дан 4 December 2019 в 06:26
поделиться

Я думаю, следующее словоблудие в разделе 26.3.1 языка C # Спецификация важна:

Подобно выражению-анонимному-методу , лямбда-выражение классифицируется как значение со специальными правилами преобразования. Значение не имеет типа, но может неявно преобразовываться в совместимый тип делегата. В частности, тип делегата D совместим с лямбда- выражение L предоставлено:
[Список исключен]

Что предотвращает компиляцию такого кода:

var doIt = () => MyMethod("test");    // CS0815

Что приводит к:

Action<object> doIt = (o) => MyMethod("test");
t = new Thread((ParameterizedThreadStart)doIt);  // CS0030

Побеждено компилятором, запрещающим преобразования из одного типа делегата в другой, даже если их подписи совместимы. Форсирование:

ParameterizedThreadStart doIt = (o) => MyMethod("test");
t = new Thread(doIt);

Что компилируется без проблем.


Нерелевантная бонусная функция: ранние тесты юзабилити на первом Apple Mac обнаружили проблему с первой версией кнопки OK в диалоговых окнах, в которых использовался шрифт без засечек. Вместо этого пользователи часто нажимали кнопку «Отмена» с некоторым видимым беспокойством. После нескольких интервью один пользователь наконец признал проблему: «Ненавижу, когда компьютер называет меня болваном!»

Что ж, компилятор, называющий вас болваном, возможно, не так уж и неуместен :)

{{1} }
2
ответ дан 4 December 2019 в 06:26
поделиться

Попробуйте:

t = new Thread(new ThreadStart(doIt));

или

t = new Thread( ()=>MyMethod("test"));

Вы пытаетесь передать аргумент типа «Action» конструктору, который принимает ThreadStart. Неявное преобразование не определено, поэтому вам нужно вручную вызвать конструктор ThreadStart.

1
ответ дан 4 December 2019 в 06:26
поделиться

Делегаты с одинаковой подписью не одинаковы в глазах CLR - это совершенно разные типы.

3
ответ дан 4 December 2019 в 06:26
поделиться

Я считаю, что это должно сработать?

Action doIt;
doIt = () => MyMethod("test");
Thread t;

t = new Thread(doIt.Invoke);
t.Start();
3
ответ дан 4 December 2019 в 06:26
поделиться

Поскольку запуск потока является отдельным типом делегата и Action не может быть преобразовано в ThreadStart .

Этот случай работает, потому что здесь ваша лямбда обрабатывается компилятором как ThreadStart:

Thread t;

t = new Thread(() => MyMethod("test"));
t.Start();
1
ответ дан 4 December 2019 в 06:26
поделиться

Вы замечаете поведение, потому что Action - это тип, а лямбда во втором рабочем примере - нет.

Возможно, они одинаковы, но вы используете конкретный тип фреймворка при использовании Action , тогда как компилятор определяет сигнатуру лямбда.

РЕДАКТИРОВАТЬ: для ясности:

Действие - это тип делегата, который является не делегатом ThreadStart, поэтому, хотя вы можете назначить лямбда-выражение для обоих, вы не можете использовать оба для запуска потока .

Во втором примере вы даете компилятору возможность определить тип делегата и, что неудивительно, он может назначить выражение lamda типу ThreadStart.

4
ответ дан 4 December 2019 в 06:26
поделиться

Основная форма этой ошибки:

delegate void d1();
delegate void d2();
d1 a;
d2 b;
b = a;

Error Cannot implicitly convert type 'ConsoleDemo1.d1' to 'ConsoleDemo1.d2'

Таким образом, вы можете «решить» свой первый пример, используя правильный тип делегата:

//Action doIt;
ThreadStart doIt;
doIt = () => MyMethod("test");
Thread t = new Thread(doIt);
3
ответ дан 4 December 2019 в 06:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: