Почему я не могу использовать / привести действие для / к ThreadStart? - PullRequest
9 голосов
/ 21 марта 2010

Оба являются делегатами и имеют одинаковую подпись, но я не могу использовать Action как ThreadStart.

Почему?

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

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

но это похоже на работу:

Thread t;

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

Ответы [ 8 ]

21 голосов
/ 22 марта 2010

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

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

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

и

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

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

Теоретически, то же самое верно и для делегатов.Вы можете иметь

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

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

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

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

4 голосов
/ 21 марта 2010

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

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

РЕДАКТИРОВАТЬ: будет ясно:

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

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

3 голосов
/ 22 марта 2010

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

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

t = new Thread(doIt.Invoke);
t.Start();
3 голосов
/ 21 марта 2010

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

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 голосов
/ 21 марта 2010

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

2 голосов
/ 22 марта 2010

Мне кажется важным следующее слово в разделе 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 в диалогах, в которых использовался шрифт без засечек. Вместо этого пользователи часто нажимали кнопку «Отмена» с явной болью. После нескольких интервью один пользователь наконец признал проблему: «Я ненавижу, когда компьютер называет меня Dolt!»

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

1 голос
/ 21 марта 2010

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

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

Thread t;

t = new Thread(() => MyMethod("test"));
t.Start();
1 голос
/ 21 марта 2010

Попробуйте:

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

или

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

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

...