2010-03-21 2 views
8

Оба являются делегатами и имеют одну и ту же подпись, но я не могу использовать Action как ThreadStart.Почему я не могу использовать/применять действие для/для ThreadStart?

Почему?

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

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

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

Thread t; 

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

Похоже Theres неявное приведение от делегата к действию, но не наоборот. Вы можете попытаться сделать это, или вы можете просто изменить 't = новый поток (doIt);' to 't = new Thread (doIt.Invoke);' для тех же результатов. –

ответ

20

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

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

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, которые позволят более структурировать типизацию. Например, функция «no pia» в C# 4 состоит в том, чтобы создать два типа, которые семантически и структурно одинаковы, но определенные в разных сборках, логически унифицировать структурно.

+0

Любые шансы делегатов могут стать структурными, т. Е. это что-нибудь сломает? –

+0

Несомненно, всевозможные вещи могут сломаться. Это будет серьезное изменение. * Любое изменение в правилах преобразования типов приводит к нарушению изменения. Если вы делаете то, что раньше было незаконным, очевидно, что это нарушение. Если вы делаете что-то незаконное, то вы можете ввести двусмысленность в разрешении перегрузки. –

+0

Можно проделать длинный путь, просто добавив перегрузки, которые принимают соответствующий вариант 'Action' /' Func' для всех API, которые принимают делегаты. Это * много * перегрузок, хотя ... В качестве дополнительной выгоды ожидаемая подпись сразу же очевидна через IntelliSense без каких-либо дополнительных настроек Visual Studio. –

1

Try:

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

или

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

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

3

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

1

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

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

Thread t; 

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

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

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); 
+0

Это не то, что я вижу. Я вижу неявное отбрасывание от делегата к действию, но не подразумеваемое отбрасывание от действия до делегата. –

+0

@Spencer, Action является (конкретным) делегатом. –

4

Поведение, которое вы заметили, связано с тем, что Action - это тип, а «лямбда» - в вашем втором рабочем примере.

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

EDIT: быть ясно:

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

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

+0

Лямбда не должна быть типом, это значение, присваиваемое типам Action/ThreadStart. Лямбда не участвует в ошибке. –

+0

Это абсолютно, Хенк. В последнем примере лямбда выведена как ThreadStart, в то время как в первом она явно вводится как Action. Последний совместим с одним из конструкторов класса Thread, а первый - нет. – Chris

+0

@ Хенк,? согласны ли вы со мной или подразумеваете, что я заявил иначе? не уверен .... –

3

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

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

t = new Thread(doIt.Invoke); 
t.Start(); 
+0

да это работает. НО! ЗАЧЕМ? И почему я могу вызвать Invoke без скобок? С тех пор, когда в стиле VB можно вызвать методы C#: D? – Rookian

+0

@Rookian: 'doIt.Invoke' автоматически заверяется делегатом' ThreadStart'. На самом деле это не вызов 'Invoke'. Это то же поведение, которое вы видите с любым другим методом, который возвращает void и не имеет параметров. –

+0

@Rookian: Во-первых, я бы предположил, что нет никакого неявного приведения из Action в делегат, потому что вы будете отличать тип, который несет меньше данных. Нет ничего, что помешало бы вам писать неявное приведение в любом случае, но разработчики .Net не должны были чувствовать, что было бы неплохо включить этот стандарт. Во-вторых, вы не вызываете эту функцию, когда вы не включаете скобки, вы передаете группу методов, которая имеет неявный приведение к делегату ThreadStart. –

2

Я думаю, что следующее словоблудие в разделе 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!»

Ну, компилятор вам звонит болван, возможно, не то, что не имеет значения :)

Смежные вопросы