Делегат это ссылка на метод. То есть если мы делаем элементарную реализацию типа такой:
public delegate void del1(string message);
...
{
del1 objdel=new del1(ShowMessage);
}
public static void ShowMessage(string message)
{
}
Это просто вызов функции. Но например если мы делаем анонимный метод типа такого:
public delegate void del1(string message);
...
{
del1 objdel=delegate(string message){
...
};
}
То в этом случае, ради такой конструкции будет создаваться отдельный класс, и в нем будет создаваться метод для выполнения? И поэтому если часто использовать такие методы то это скажется негативно на производительности, так?
Когда ты объявляешь тип делегата, компилятор генерирует код класса-делегата (который наследуется от MulticastDelegate) - это и есть его сущность. Благодаря статической типизации компилятор может еще на этапе компиляции проверить соответствие сигнатуры метода, который ты передал в конструктор класса делегата (new Action(method), например), самому делегату. Я не знаю как на низком уровне работают делегаты, но однозначно перед непосредственным выполнением обернутого в делегат метода существует несколько дополнительных операций: Action act; act() или act.Invoke() - одно и то же. act() компилятором преобразуется в act.Invoke() - своего рода синтаксический сахар. Так вот о накладных операциях: в стеке появится вызов Invoke -> возможно еще чего-то -> самого метода. В последнем абзаце я думаю ты имел ввиду это:
Вот такой код:
using System;
namespace tester
{
class Program
{
static void Main(string[] args)
{
var act = new Action(() => Console.Write(true));
}
}
}
Скомпилируется в -
// tester.Program
using System;
using System.Runtime.CompilerServices;
using tester;
internal class Program
{
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();
public static Action <>9__0_0;
internal void <Main>b__0_0()
{
Console.Write(true);
}
}
private static void Main(string[] args)
{
//Здесь проверяется создан ли экземпляр делегата, и его создание в противном случае.
//Таким образом, можно говорить, что создание делегата для анонимной ф-ции выполняется один раз за работу программы (если нет захвата внешних переменных).
Action act = <>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Action(<>c.<>9.<Main>b__0_0));
}
}
Стоит сказать, что разницы нет, что метод в другом классе. Такой код чище выходит:
class Program
{
static void Print() => Console.Write(true);
static void Main(string[] args)
{
var act = new Action(Print);
}
}
Декомпиляция:
// tester.Program
using System;
internal class Program
{
private static void Print()
{
Console.Write(true);
}
private static void Main(string[] args)
{
Action act = Print;
}
}
И посмотрим на его IL:
А в этом случае при каждом вызове метода заново создается экземпляр делегата. В предыдущем, напомню, у нас каждый раз при вызове метода проверяется создавали ли мы уже экземпляр (причем поля-то были статичные). Что дороже решайте сами (я так думаю проверка легковесней). Но оптимизировать код так: экономия на спичках. И еще один момент, зачем все-таки создается класс под анонимную ф-цию? Для захвата внешних переменных. Пример:
using System;
namespace tester
{
class Program
{
static void Main(string[] args)
{
int count = 1;
var del = new Action(delegate () {
Console.WriteLine(count);
});
}
}
}
Сигнатура Action - безпараметричная - ф-ция. Но фактически внутри идет вывод значения count, то есть передача параметра. Для этого и создается обертка. Видим:
// tester.Program
using System;
using System.Runtime.CompilerServices;
using tester;
internal class Program
{
[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
public int count;
internal void <Main>b__0()
{
Console.WriteLine(count);
}
}
private static void Main(string[] args)
{
//Создается класс-обертка, которая захватит внешние переменные метода.
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
//Захват переменной. Тут даже оптимизация (count - не переменная метода, а экземпляра оболочки для захвата)
<>c__DisplayClass0_.count = 1;
//Создание экземпляра.
Action del = new Action(<>c__DisplayClass0_.<Main>b__0);
}
}
Кстати, когда используется экземплярный метод для оборачивания делегатом, тогда происходит захват и самой сущности (экземпляра то есть). Вот сигнатура конструктора Action: Первый аргумент: ссылка this для экземплярного метода или null, если метод неэкземплярный. А второй - IntPtr-ссылка на метод. Вот кусочек из первого примера: Что можно сказать на последок... Вызов делегата - это не просто вызов ф-ции. Автоматический захват внешних переменных - классно и удобно. Раньше еще использовали анонимные ф-ции не только для передачи куда-то (например, для обработчика события), а еще в роли локальных ф-ций. Удобно это потому, что анонимные ф-ции позволяли выполнять захват внешних переменных и не приходилось писать огромную тучу аргументов для вызова. То есть:
using System;
namespace tester
{
class Program
{
static void Main(string[] args)
{
int count = 1;
var inc = new Action(() => count += 15);
inc();
inc();
}
}
}
Вместо того, чтобы сделать ф-цию void inc(int count) - т.к. это более громоздко. Но недавно в C# ввели локальные ф-ции и теперь это работает более оптимально, нежели когда это реализовывалось посредством делегатов:
using System;
namespace tester
{
class Program
{
static void Main(string[] args)
{
int count = 1;
void printCount() => Console.WriteLine(count);
printCount();
}
}
}
Скомпилируется в -
// tester.Program
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using tester;
internal class Program
{
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <>c__DisplayClass0_0
{
public int count;
}
private static void Main(string[] args)
{
<>c__DisplayClass0_0 <>c__DisplayClass0_ = default(<>c__DisplayClass0_0);
<>c__DisplayClass0_.count = 1;
<Main>g__printCount|0_0(ref <>c__DisplayClass0_);
}
[CompilerGenerated]
internal static void <Main>g__printCount|0_0(ref <>c__DisplayClass0_0 P_0)
{
Console.WriteLine(P_0.count);
}
}
Вот тут действительно просто вызов ф-ции + структура для захвата внешних аргументов. И отвечая на твой финальный вопрос: нет. Эти 2 (с использованием анонимок и оборачиванием ф-ции класса) варианта особо не уступают друг другу в производительности. Мне еще нравится юзать локальные ф-ции для оборачивания в делегаты - более стройный код получается. Интересно во что это компилится. Но проверять не буду.
Делегат - это также специальный класс, унаследованный от MulticastDelegate.
Вообще, чтобы присвоить анонимный метод чему-то, надо, чтобы это что-то имело явно определённый тип:
using System;
namespace CSrharpApplicationTest
{
internal class Program
{
private static void Main(string[] args)
{
Action x = delegate () // С var не сработает.
{
};
}
}
}
Так происходит потому что может возникнуть неоднозначность, как в следующем случае:
namespace CSrharpApplicationTest
{
internal class Program
{
public delegate void A();
public delegate void B();
private static void Main(string[] args)
{
var x = delegate () // Что выбрать в качестве типа x - A или B? Компилятору не ясно.
{
};
}
}
}
Хочется заметить, что может возникнуть вопрос такого рода: Почему в местах где нет неоднозначности такое не разрешили? Я думаю, что так поступили для большей стандартизации кода и облегчения его написания. Ибо если бы разрешили такое поведение с var
, то при добавлении ссылки на пространство имён, содержащем делегат с похожей сигнатурой могла возникнуть неоднозначность, что означало бы дополнительное время на правку кода.
Возвращаясь к первому примеру, в нём я создаю неявно экземпляр делегата Action
со ссылкой на метод, содержимого которого заключено в {}
. То есть, здесь (в контексте этого примера) всё происходит ровно также как и без использования анонимных методов:
using System;
namespace CSrharpApplicationTest
{
internal class Program
{
public static void Method()
{
}
private static void Main(string[] args)
{
Action x = Method;
}
}
}
Так что с точки производительности различий не будет. Следует понимать, что делегаты - это более безопасное средство для ссылок на методы, нежели указатели в C++, за что приходится платить производительностью в некоторой степени.
Обращаю внимание на то, что обычно указывают тип переменной явно, а не используют var
.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Пишу бота для ВК, когда запрашиваю информацию об 1 человеке из группы, всё работает - получаю имя и фамилиюЕсли запрашиваю информацию сразу...
Во вкладках Visual Studio нашел вкладку, где показывает, что у меня на компьютере установлен сервер MS SQL, хотя я его не устанавливал:
В книге про тип данных VARCHAR сказано, что если присвоить строковое значение длиннее позволенного, то оно будет усечено до максимальной длины,...