Правильный ответ - 1. Почему так? Давайте разбираться
Сравнение делегатов
Сперва нам кажется, что тут всё просто.
Но делегат - это непростой тип в .NET. Давайте посмотрим, из каких полей он состоит.
Method
Это поле с типом данных MethodInfo, который должен быть вызван при обращении к делегату. В памяти он представлен через внутренние поля
Target
Это объект, к которому привязан метод, если он нестатический. То есть если метод — экземплярный,
У делегатов так же переопределён метод Equals. В репозитории reference source можно увидеть полную его реализацию, и нас интересует следующий фрагмент:
Приватные поля
Если мы вызовем этот метод в цикле из примера выше, то обнаружим одинаковые адреса делегата на каждой итерации цикла.
Как вы думаете, почему так?
Flexible Coding
Сравнение делегатов
Сперва нам кажется, что тут всё просто.
Func<int, int> function = x => i * x;
- это создание новой переменной. На основе документации Microsoft узнаём, что делегат - это reference type, значит они сравниваются по ссылке. А раз мы на каждой итерации цикла создаём новую переменную function
- у неё каждый раз будет новый адрес в памяти и Contains всегда вернёт false.Но делегат - это непростой тип в .NET. Давайте посмотрим, из каких полей он состоит.
Method
Это поле с типом данных MethodInfo, который должен быть вызван при обращении к делегату. В памяти он представлен через внутренние поля
_methodPtr
и _methodPtrAux
, которые являются указателями на конкретную функцию.Target
Это объект, к которому привязан метод, если он нестатический. То есть если метод — экземплярный,
Target
указывает на объект, для которого этот метод будет вызван. Если метод статический, Target
может быть null
.У делегатов так же переопределён метод Equals. В репозитории reference source можно увидеть полную его реализацию, и нас интересует следующий фрагмент:
// do an optimistic check first. This is hopefully cheap enough to be worth
if (_target == d._target && _methodPtr == d._methodPtr && _methodPtrAux == d._methodPtrAux)
return true;
Приватные поля
_target
, _methodPtr
и _methodPtrAux
могут нам указывать на то, что два делегата равны. Они как раз дают нам информацию о методе и привязке метода к объекту. Попробуем вытащить их из делегата (тут нам пригодится немного магии unsafe):
void PrintFunctionInfo(Func<int, int> function)
{
unsafe
{
TypedReference tr = __makeref(function);
IntPtr* ptr = (IntPtr*)*(IntPtr*)&tr;
Console.WriteLine($"Pointer to Delegate: 0x{(ulong)ptr:X}");
IntPtr target = ptr[1];
IntPtr methodPtr = ptr[3];
IntPtr methodPtrAux = ptr[4];
Console.WriteLine($"Target: 0x{(ulong)target:X}");
Console.WriteLine($"MethodPtr: 0x{(ulong)methodPtr:X}");
Console.WriteLine($"MethodPtrAux: 0x{(ulong)methodPtrAux:X}");
}
}
Если мы вызовем этот метод в цикле из примера выше, то обнаружим одинаковые адреса делегата на каждой итерации цикла.
Как вы думаете, почему так?
Flexible Coding
Всем привет! Вот и наступило лето, а у нас - итоги прошедшего сезона!
Этой весной мы:
💥 Контролируемо ломали MongoDB - первая и вторая части
😤 Расстраивались из-за Open Source, который становится всё дороже
🧠 Закончили цикл статей про многопоточность (или нет???) - 2, 3 и 4
🔀 Сравнивали делегаты
❗ Пропускали точку с запятой в SQL
🤖 А так же смотрели на ИИ и рисовали схемки в Excalidraw
Flexible Coding
Этой весной мы:
💥 Контролируемо ломали MongoDB - первая и вторая части
😤 Расстраивались из-за Open Source, который становится всё дороже
🧠 Закончили цикл статей про многопоточность (или нет???) - 2, 3 и 4
🔀 Сравнивали делегаты
❗ Пропускали точку с запятой в SQL
🤖 А так же смотрели на ИИ и рисовали схемки в Excalidraw
Flexible Coding