:: Не фоксом единым
Re: Делегат
Igor Korolyov
Автор

Сообщений: 34580
Дата регистрации: 28.05.2002
S-type
Вывод типа, это когда переменная неизвестного типа слева, а справа - известного.
Не только. Автоматически выводятся типы для generic методов и выводятся типы делегатов (конечно если система МОЖЕТ вывести тип - в общем случае это не всегда возможно) для переданных методов (в т.ч. анонимных и лямбд).
Анонимный метод или statement-лямбда "сами по себе" не имеют никакого типа. Поэтому их невозможно использовать напрямую - компилятор всегда должен вывести тип - в частности при использовании их как делегатов нужно вывести тип делегата.
В строке new Thread(что_то) компилятор видит вызов конструктора класса Thread. Среди всех конструкторов есть лишь 2 принимающих 1 параметр - и это параметр типа ThreadStart либо же ParameterizedThreadStart (оба этих типа - делегаты, но у них разная сигнатура - void () у первого и void (object) у второго).
Соответственно компилятор знает, что параметром должен быть либо один либо второй делегат, и ему остаётся только выбрать какой из них подходит в данном конкретном случае. У анонимного метода есть частичная сигнатура - т.е. известно какие параметры он принимает - в твоём примере будет пустой список параметров -> по сигнатуре подходит делегат ThreadStart. Именно его и будет использовать компилятор.
Вообще исходный код (синтаксис с лямбдой обрабатывается совершенно аналогично)
Thread t1 = new Thread(delegate ()
{
Console.WriteLine("Hello, World!");
});
t1.Start();
Преобразуется в
internal static class Program
{
public static void Main(string[] args)
{
ThreadStart arg_20_0;
if ((arg_20_0 = Program.<>c.<>9__0_0) == null)
{
arg_20_0 = (Program.<>c.<>9__0_0 = new ThreadStart(Program.<>c.<>9.<Main>b__0_0));
}
Thread t = new Thread(arg_20_0);
t.Start();
}
[CompilerGenerated]
[Serializable]
private sealed class <>c
{
static <>c()
{
// Note: this type is marked as 'beforefieldinit'.
}
public <>c()
{
}
internal void <Main>b__0_0()
{
Console.WriteLine("Hello, World!");
}
public static readonly Program.<>c <>9 = new Program.<>c();
public static ThreadStart <>9__0_0;
}
}
Что мы видим в этом коде? Сам анонимный метод успешно описан как <Main>b__0_0 в сгенерированном классе <>c
В нём же описано поле <>9__0_0 для хранения делегата - оно строго типизировано, и в main прописан код который инициализирует данное поле, если оно пусто (это позволяет не создавать по новой копии делегата при каждом использовании анонимного метода). Переменная arg_20_0 в main на самом деле "виртуальная" - это так декомпилятор восстановил IL код где просто на стек была помещена ссылка на сей делегат. В сами тонкости инициализации, создание экземпляра этого скрытого класса можно не вникать. Главное что мы видим, это совершенно чёткая строгая типизация. Тип делегата однозначно определён, сигнатура анонимного метода - тоже.

S-type
В случае
Thread t1 = new Thread(myDelegate);
Компилятор ругается:
Цитата:
Аргумент 1: не удается преобразовать из "test3.Program.MyDelegate" в "System.Threading.ThreadStart".
Может, проблема не в выводе типов, а в приведении типов?
Ещё раз - типы делегатов не приводятся. В принципе нет для них никаких cast-ов.
Компилятор просто сообщает что там где ожидалось ThreadStart он увидел MyDelegate. Да, если бы это были не делегаты, то он бы попытался сделать неявное приведение типов. Возможно даже преуспел бы в этом А так - ну просто не совсем чёткое сообщение. Было бы неразумно расплываться мыслию по древу в сообщении об ошибке, приводя там массу информации о том что где "не совпало" и как с этим быть дальше. Описан лишь самый типичный случай проблемы "несоответствия типов".

S-type
При этом msdn.microsoft.com этом описание
public delegate void ThreadStart()
Т.е. как бы очень похоже на
delegate void MyDelegate();
и, нет каких то причин "брыкаться" по поводу типов.
Похожи сигнатуры "оборачиваемых" методов. Даже более того, они совпадают! Однако сами типы ни разу не идентичны. У них разные имена
Или, по твоему,
class A {}
и
class B {}
Это тоже "одно и то же" на основании того что там одинаковый (пустой) список членов класса, и один общий предок object? И можно спокойно присвоить экземпляр типа B переменной типа A?
Потому я и отправил тебя снова читать основы про то что такое тип
S-type
Не ужели потому, что ThreadStart является дочерним для delegate?
Нет, потому что компилятор умеет выводить типы делегатов для анонимных методов. Кстати, он умеет и создавать обёртки (делегаты) при указании "просто имени метода" в том месте где должен быть делегат (т.е. выводить тип делегата для НЕ-анонимных методов).
Например для событий уже давно необязательно писать "формально полную" конструкцию
KeyPress += new KeyPressEventHandler(text1_KeyPress);
хватает сокращённой
KeyPress += text1_KeyPress;
Компилятор сам "догадается" создать делегат нужного типа KeyPressEventHandler и поместить в него ссылку на соответствующий метод text1_KeyPress (скомпилированный код в этих 2 случаях будет идентичным).
На самом деле это по сути одна и та же возможность компилятора...
S-type
Как же преобразовать?
Никак не "преобразовать". Можно лишь создать новый экземпляр [нового] типа делегата и передать ему в конструктор "старый" экземпляр. Тогда в новом экземпляре в списке вызова окажутся те же самые методы, включая анонимные что и в старом (там есть свои небольшие нюансы, но пока что тебе явно не до них будет ).


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Делегат
S-type

Сообщений: 2969
Дата регистрации: 24.04.2004
Igor Korolyov
Вообще исходный код (синтаксис с лямбдой обрабатывается совершенно аналогично)
[...]


Спасибо за пример, и комментарии к нему. Не с первого раза, но (кажется) смог его "осилить"

Вот как создателям C# приходится выкручиваться, что бы "и рыбку съесть, и косточкой не подавиться" - и сильную типизацию соблюсти (скажем так, относительно сильную), и что бы быстро работало.
Ratings: 0 negative/0 positive
Re: Делегат
Igor Korolyov
Автор

Сообщений: 34580
Дата регистрации: 28.05.2002
Не так, не
S-type
что бы быстро работало
, а что бы быстро писалось ленивыми программерами Те же анонимные методы (соответственно и лямбды) субоптимальны. "Вручную" получится меньше инструкций и, тем паче, "сущностей" (автосозданные классы, плюс поля в них для "захвата" переменных, если таковой захват требуется).
Про yield return и async/await лучше вообще промолчать - там ГОРЫ кода генерируются
Но, с другой стороны, компы нынче быстрые, а время программиста дорогое...

Да, конечно, некоторые виды синтаксического сахара практически не влияют на создаваемый код - те же var к примеру. Они лишь замедляют процесс компиляции, что IMHO разумно допустимое зло А некоторые - скажем инициализаторы массивов типа int могут даже и ускорить создаваемый код.


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Делегат
S-type

Сообщений: 2969
Дата регистрации: 24.04.2004
Есть код

delegate void MyDelegate();
static void MyFunc()
{
Console.WriteLine("Hello, World");
}
static void Main()
{
MyDelegate myDelegate1 = new MyDelegate(MyFunc);
}

Можно и так:

MyDelegate myDelegate1 = MyFunc;

Упрощённая конструкция - это ведь синтаксический сахар?

Вопрос, почему работает конструкция

MyDelegate myDelegate2 = delegate()
{
Console.WriteLine("Hello, World");
};

Потому, что она преобразуется в

MyDelegate myDelegate2 = new MyDelegate(delegate()
{
Console.WriteLine("Hello, World");
});

Или, потому что тип MyDelegate унаследован от Delegate?
Ratings: 0 negative/0 positive
Re: Делегат
Igor Korolyov
Автор

Сообщений: 34580
Дата регистрации: 28.05.2002
S-type
Можно и так:
MyDelegate myDelegate1 = MyFunc;
Упрощённая конструкция - это ведь синтаксический сахар?
Да. Она компилируется в такой же код как и верхняя. Компилятор подставляет вызов конструктора делегата new MyDelegate() передавая ему внутренний рантаймовский указатель на метод (и на сам объект, если метод не статический).
S-type
Вопрос, почему работает конструкция
Не понимаю смысла вопроса... Работает потому что так работает компилятор в соответствии со спецификацией языка C#...
S-type
Потому, что она преобразуется в
...
Или, потому что тип MyDelegate унаследован от Delegate?
Во что она преобразуется я показал выше - в автогенерируемый класс, метод в этом классе, ряд полей и определённый код инициализации.
При чём тут факт того что "тип MyDelegate унаследован от Delegate" я не понимаю. Любой делегат унаследован в частности и от этого типа, равно как и от типа System.MulticastDelegate, только какое это имеет отношение к тому как работает компилятор?
Ну это как спросить: "int i = 1; работает потому что int унаследован от типа System.Object?" Да он унаследован, но работает конструкция вовсе не по этой причине.
Delegate специальный класс - как и другие классы в Common Type System он не доступен напрямую (нельзя создать свой класс на его основе, хотя он и не "запечатан" aka sealed), и используется только специальными конструкциями CLS языков, в частности синтаксическими конструкциями с ключевыми словами delegate и event в C#.


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Делегат
S-type

Сообщений: 2969
Дата регистрации: 24.04.2004
Igor Korolyov
S-type
Вопрос, почему работает конструкция
Не понимаю смысла вопроса... Работает потому что так работает компилятор в соответствии со спецификацией языка C#...

Вот что мне мозг взрывает:

// это сахар
MyDelegate myDelegate1 = MyFunc; // MyDelegate myDelegate1 = new MyDelegate(MyFunc);
MyDelegate2 myDelegate2 = MyFunc; // MyDelegate2 myDelegate2 = new MyDelegate2(MyFunc);
// это уже НЕ сахар
MyDelegate myDelegate3 = myDelegate1; // так можно, потому что myDelegate3 и myDelegate1 одного типа
MyDelegate2 myDelegate4 = myDelegate1; // так нельзя, потому что myDelegate4 и myDelegate1 разного типа типа
MyDelegate2 myDelegate5 = delegate () { }; // так можно, потому что "delegate () { }" - не имеет типа

Выведение типов, это когда для выражения определяется тип.

Console.WriteLine(((1).GetType()).FullName); // 1

А код

Console.WriteLine(((delegate () { }).GetType()).FullName); // ошибка

приводит к ошибке компиляции

Цитата:
Оператор "." невозможно применить к операнду типа "анонимный метод"

Так же, к ошибке приводит

var myDelegate = delegate () { };

Т.е. для выражения "delegate () { }" то же определяется тип. Но, тип определяется не по тому, что находится в самом выражении!



Исправлено 1 раз(а). Последнее : S-type, 06.03.17 13:41
Ratings: 0 negative/0 positive
Re: Делегат
S-type

Сообщений: 2969
Дата регистрации: 24.04.2004
А вот так - работает

Console.WriteLine((((MyDelegate)delegate () { }).GetType()).FullName);
Ratings: 0 negative/0 positive
Re: Делегат
Igor Korolyov
Автор

Сообщений: 34580
Дата регистрации: 28.05.2002
Ну я уж не знаю как тебе объяснить разницу между КОДОМ и делегатом... У КОДА нет никакого типа - независимо от того обычный это метод или анонимный. У него есть только сигнатура - возвращаемый кодом тип и типы принимаемых им параметров.
У делегата же тип есть. Собственно говоря, делегат это и есть особый вид типов в CTS.
Код нельзя никуда передать, получить на него ссылку, получить его "свойства". Делегат - можно. Он для того и создан чтобы можно было передавать "код" как объект. В с++ для этого применялись просто указатели на функции, но они, как и другие указатели нетипобезопасны. Делегат - это типобезопасный указатель на некоторый код.
В переменную типа делегат вполне можно присвоить значение другой переменной - лишь бы она была совместимого типа (про Variance лучше пока даже не вспоминать...) Что при этом может "взрывать мозг" я не понимаю...

И да, система автоматически создаёт делегаты в НЕКОТОРЫХ случаях.
MSDN
An anonymous-method-expression and a lambda-expression is classified as a value with special conversion rules. The value does not have a type but can be implicitly converted to a compatible delegate type.
msdn.microsoft.com


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Делегат
Igor Korolyov
Автор

Сообщений: 34580
Дата регистрации: 28.05.2002
S-type
Т.е. для выражения "delegate () { }" то же определяется тип. Но, тип определяется не по тому, что находится в самом выражении!

Не определяется тип для анонимного метода (так же как и для лямбды) - нет у него никакого типа! Вот если этот метод будет обёрнут в делегат, то у него тип будет.


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Делегат
S-type

Сообщений: 2969
Дата регистрации: 24.04.2004
Igor Korolyov
В переменную типа делегат вполне можно присвоить значение другой переменной - лишь бы она была совместимого типа

Вот именно - "совместимого типа"!

В выражении:

MyDelegate myDelegate5 = delegate () { };

в переменную типа MyDelegate присваивается переменная какого типа?

Само по себе выражение "delegate () { }" ведь типа не имеет?

Вот в чём когнитивный диссонанс
Ratings: 0 negative/0 positive
Re: Делегат
Igor Korolyov
Автор

Сообщений: 34580
Дата регистрации: 28.05.2002
Естественно типа MyDelegate. Выше цитата из MSDN, я даже специально выделил ключевое...

В данном случае переменной присваивается НЕ другая переменная, а именно свежесоздаваемый делегат, оборачивающий этот самый анонимный метод.


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Делегат
S-type

Сообщений: 2969
Дата регистрации: 24.04.2004
Igor Korolyov
В строке new Thread(что_то) компилятор видит вызов конструктора класса Thread. Среди всех конструкторов есть лишь 2 принимающих 1 параметр - и это параметр типа ThreadStart либо же ParameterizedThreadStart (оба этих типа - делегаты, но у них разная сигнатура - void () у первого и void (object) у второго).

Можно написать так:

using System.Threading;
// ...
public static void RunMe()
{
Console.WriteLine("Вспомогательный поток");
}
static void Main()
{
Thread t = new Thread(RunMe);
t.Start();
Console.WriteLine("Основной поток");
}

На msdn.microsoft.com указаны 4-ре конструктора. Я правильно понимаю, что в случае

Thread t = new Thread(RunMe);

происходит неявное преобразование:

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

Надо ли явно указывать:

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



Исправлено 1 раз(а). Последнее : S-type, 10.04.17 12:25
Ratings: 0 negative/0 positive
Re: Делегат
S-type

Сообщений: 2969
Дата регистрации: 24.04.2004
MyDelegate myDelegate5 = delegate () { };

Igor Korolyov
В данном случае переменной присваивается НЕ другая переменная, а именно свежесоздаваемый делегат, оборачивающий этот самый анонимный метод.

Оборачивающий, это

MyDelegate myDelegate5 = new MyDelegate( delegate() { });

Может всё таки, неявно преобразующий (на подобии)

MyDelegate myDelegate5 = (MyDelegate) delegate() { };
?


Исправлено 1 раз(а). Последнее : S-type, 10.04.17 12:24
Ratings: 0 negative/0 positive
Re: Делегат
Igor Korolyov
Автор

Сообщений: 34580
Дата регистрации: 28.05.2002
Ещё раз - делегаты НЕ "преобразуются". Ни явно, ни неявно. Можно лишь создать новый делегат взяв "указатель на метод" у старого/существующего делегата. Либо же просто взяв "указатель на метод" который (метод) получается из анонимного блока кода.

Иногда делегаты создаются неявно - вот как раз
new Thread(RunMe);
Это и есть неявное создание делегата, этот код полностью аналогичен
new Thread(new ThreadStart(RunMe));


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Делегат
S-type

Сообщений: 2969
Дата регистрации: 24.04.2004
Igor Korolyov
Ещё раз - делегаты НЕ "преобразуются".

Почитал - да, ты прав. написано, что экземпляры делегатов всегда неизменяемы (immutable).

Почему же тогда компилятор не ругается на

MyDelegate myDelegate5 = (MyDelegate) delegate() { };

? Приведение типа, это ведь преобразование?



Исправлено 1 раз(а). Последнее : S-type, 10.04.17 15:17
Ratings: 0 negative/0 positive
Re: Делегат
Igor Korolyov
Автор

Сообщений: 34580
Дата регистрации: 28.05.2002
Видимо потому что там ничего никуда не "приводится".
Компилятор вообще-то явно говорит что в данном месте конструкция (MyDelegate) лишняя.
Равно как, к примеру
int i = (int)5;
приведение типа лишнее, компилятор его попросту выбрасывает при генерации кода.
Разница лишь в том, что в случае делегата компилятор в любом случае будет вызывать конструктор этого самого делегата, передавая туда "указатель" на анонимный метод (внутри скомпилированной сборки метод будет уже совсем не "анонимным"). Указатель на метод внутри IL имеет тип "native int" - и конструктор делегата принимает именно этот тип (в частности поэтому нельзя его напрямую вызывать/создавать). Твои попытки "подсказать" компилятору что за этим самым "указателем типа native int" (анонимный метод) будет метод который требуется обернуть в делегат типа MyDelegate излишни - компилятор и сам это знает исходя из левой части выражения.
Есть ситуации когда компилятор этого не знает (не может вывести тип делегата для оборачивания какого-либо метода - анонимного или нет, без разницы). Например вызов Control.BeginInvoke() или твой старый пример с вызовом obj.GetType(). Когда компилятор видит что ожидается тип Delegate (НЕ "конкретный тип-наследник" скажем MyDelegate а именно "базовый" - т.е. можно передать туда абсолютно любой делегат) или тип Object (т.е. подходит вообще любой тип), он попадает в тупик. Он не может создать делегат типа Delegate или MulticastDelegate, т.к. это абстрактные типы, разрешены лишь их "наследники". А какой именно тип-наследник выбрать? Вот тут-то подсказка в виде (MyDelegate) и спасает - хотя никаких "приведений типа" она не делает, но компилятору уже становится ясно что нужен именно тип MyDelegate и он совершенно спокойно генерирует код new MyDelegate(оборачиваемый_метод).


------------------
WBR, Igor
Ratings: 0 negative/0 positive


Извините, только зарегистрированные пользователи могут оставлять сообщения в этом форуме.

On-line: 8 (Гостей: 8)

© 2000-2024 Fox Club 
Яндекс.Метрика