- Известно, что при размещении объекта ссылочного типа в куче у него есть указатель на объект-тип (область памяти, содержащую статические поля и реализацию статических методов). Этот объект-тип содержит индекс блока синхронизации и еще один указатель на объект-тип. Зачем он нужен и куда указывает?
ОтветВ CLR каждый объект в куче имеет указатель на объект-тип. Это нужно для того, чтобы, например, найти значения статических полей и реализацию статических методов для экземпляра типа. Но объект-тип, на который ссылается экземпляр типа так же имеет ссылку на объект-тип и является «экземпляром» для объекта-типа System.Type, объект-тип для которого создается CLR при запуске.
На этой схеме объект Manager ссылается на объект-тип Manager, указатель на объект-тип которого ссылается на объект-тип System.Type.
- Можно ли объявить делегат не только внутри класса, но и в глобальной области видимости? Почему?
ОтветМожно. Делегат представляет из-себя не просто обертку для метода, а полноценный класс, а класс можно сделать как вложенным в родительский класс, так и просто объявить в глобальной области видимости. То есть делегат можно определить везде, где может быть определен класс.
internal class Feedback : System.MulticastDelegate { // Конструктор public Feedback(Object object, IntPtr method); // Метод, прототип которого задан в исходном тексте public virtual void Invoke(Int32 value); // Методы, обеспечивающие асинхронный обратный вызов public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object object); public virtual void EndInvoke(IAsyncResult result); }
Еще интересный вопрос — почему конструктор класса делегата содержит два параметра, а в коде мы просто передаем указатель на метод (внутрений для CLR, по которому этот метод она найдет)?
delegate void Test(int value); void A(int v) { Console.WriteLine(v); } void TestDelegate() { var t = new Test(A); t(1); }
Все просто — потому что компилятор при создании делегата сам подставляет в конструктор значение параметра оbject. Если метод, которым инициализируется делегат статический, то передается null. Иначе передается объект экземпляра класса, которому принадлежит метод. В этом случае состояние этого объекта может быть изменено через ключевое слово this внутри метода.
- Простой вопрос — что выведет на экран метод Test и почему?
delegate int GetValue(); int Value1() { return 1; } int Value2() { return 2; } void Test() { var v1 = new GetValue(Value1); var v2 = new GetValue(Value2); var chain = v1; chain += v2; Console.WriteLine(chain()); }
ОтветВыведет 2. При помещении делегатов в цепочку у делегата chain заполняется внутреннее поле, которое представляет из себя массив делегатов (в случае, если количество больше одного, иначе просто хранится ссылка на метод). Все делегаты выполняются последовательно. Возвращается значение последнего, остальные не учитываются.
- Объясните, каким образом локальные переменные pass1 и pass2 из метода Test передаются в лямбда-выражение, если WaitCallback принимает лишь один параметр(и в данном случае ссылка на него равна null).
namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var p = new Program(); p.Test(); Console.ReadKey(); } void Test() { int pass1 = 5; object pass2 = "Passing test"; ThreadPool.QueueUserWorkItem((obj) => { Console.WriteLine(pass1); Console.WriteLine(pass2); }); } } }
ОтветДля того, чтобы в этом разобраться, открываем сборку в ildasm.
Можете убедиться, что в этом случае лямбда выражение — это не метод, а целый класс!
.method private hidebysig instance void Test() cil managed { // Размер кода: 44 (0x2c) .maxstack 2 .locals init ([0] class ConsoleApplication1.Program/'<>c__DisplayClass1_0' 'CS$<>8__locals0') IL_0000: newobj instance void ConsoleApplication1.Program/'<>c__DisplayClass1_0'::.ctor() IL_0005: stloc.0 IL_0006: nop IL_0007: ldloc.0 IL_0008: ldc.i4.5 IL_0009: stfld int32 ConsoleApplication1.Program/'<>c__DisplayClass1_0'::pass1 IL_000e: ldloc.0 IL_000f: ldstr "Passing test" IL_0014: stfld object ConsoleApplication1.Program/'<>c__DisplayClass1_0'::pass2 IL_0019: ldloc.0 // вот создается этот класс! IL_001a: ldftn instance void ConsoleApplication1.Program/'<>c__DisplayClass1_0'::'<Test>b__0'(object) IL_0020: newobj instance void [mscorlib]System.Threading.WaitCallback::.ctor(object, native int) IL_0025: call bool [mscorlib]System.Threading.ThreadPool::QueueUserWorkItem(class [mscorlib]System.Threading.WaitCallback) IL_002a: pop IL_002b: ret } // end of method Program::Test
А вот описание самого класса и он содержит обсуждаемый метод:
Компилятор определяет, есть ли в лямбда выражении ссылки на локальные переменные. Если нет, то генерируется статический метод (или экземплярный, если в лямбда-выражении присутствуют ссылки на члены экземпляра типа). А если ссылки на локальные переменные присутствуют, то генерируется класс, содержащий нужные поля и метод, описанный в лямбда выражении.
- Что выведет на экран следующий код?
int a = -5; Console.WriteLine(~a);
ОтветВыведет 4. Оператор ~ производит побитовую реверсию.
Console.WriteLine("{0:x8}, {1:x8}", -5, ~(-5)); // выведет fffffffb, 00000004
Причем для значения 5 выведет -6.
- Обычно управлять в ручную уборкой мусора не рекоммендуется. Почему? Приведите пример, когда вызов метода GC.Collect() имеет смысл.
ОтветДело в том, что уборщик мусора сам настраивает пороговые значения для поколений (в зависимости от реального поведения приложения). Как только размер поколения в управляемой куче превышает пороговый, начинается уборка мусора (об этом очень подробно написано в Рихтере). Поэтому чаще всего следует избегать вызовов GC.Collect(). Но может возникнуть необходимость ручной уборки мусора, если произошло разовое событие, которое привело к уничтожению множества старых объектов. Таким образом, основанные на прошлом поведении приложения прогнозы уборщика мусора окажутся не точными, а уборка мусора окажется весьма кстати.
- Бонус с собеседования: есть метод rand2, выдающий 0 или 1 с одинаковой вероятностью. Написать метод rand3, использующий метод rand2, выдающий 0,1,2 с одинаковой вероятностью.
Ответ// первое решение int rand3() { int x, y; do { x = rand2(); y = rand2(); } while (x == 0 && y == 1); return x + y; } // второе решение int rand3() { int r = 2 * rand2() + rand2(); if (r < 3) return r; return rand3(); }
Любая критика приветствуется. Вопросы есть еще по другим темам, если интересно.
Комментарии (49)
master65
15.06.2017 16:34+1// первое решение
int rand3()
{
int x, y;
do {
x = rand2();
y = rand2();
} while (x == 0 && y == 1);
return x + y;
}
Но это всегда вернет 1sir_Maverick
15.06.2017 16:53не, 25% — 0 и 0, 25% — 1 и 1, 25% — 1 и 0 и 25% — 0 и 1. While как раз откинет одну пару 0 и 1 для равновероятного появление 0 1 и 2.
sir_Maverick
15.06.2017 16:40return (rand2() + rand2()+ rand2()+ rand2()+ rand2())%3
sdev
15.06.2017 18:16+1Увы нет. В скобках 2^5 вариантов, т.е. 32 различных варианта. Это не кратно 3.
shrstk
15.06.2017 18:18Тут распределение не будет равномерным — единичка выпадает на примерно 3% реже, чем 0 и 2
sir_Maverick
16.06.2017 01:15-1Разве вероятность появления в скобках числа от нуля до пяти не будет одинакова для каждого числа?
mayorovp
16.06.2017 15:18+1С чего бы? Ваши вероятности для того, что в скобках — такие:
1/32, 5/32, 10/32, 10/32, 5/32, 1/32
По модулю 3 получаются более равномерные, но все еще не равные вероятности:
11/32, 10/32, 11/32
sir_Maverick
16.06.2017 15:23Можете рассказать, как вы это считали? Какие формулы и правила использовали? На самом деле интересно, т.к. не знаком с тервером практически никак.
mayorovp
16.06.2017 20:05+1Получить 0 или 5 в скобках можно только 1 способом — все слагаемые должны быть 0 или 1 соответственно.
Получить 1 или 4 можно 5 способами — одно из слагаемых должно отличаться от остальных.
Получить 2 или 3 можно 10 способами — потому что 5!/2!3!, подробно объяснять лень.
EreminD
15.06.2017 17:05int rand3() { switch (rand2().ToString() + rand2().ToString()) { case "00": return 0; break; case "01": return 1; break; case "10": return 2; break; case "11": return rand3(); break; default: return rand3(); } }
Zam_dev
15.06.2017 18:16int rand3() { int v= (rand2() + rand2()) { return v>2? rand3(): v }
sdev
15.06.2017 18:22rand2() + rand2() никогда не больше 2
Zam_dev
15.06.2017 22:13Судя по тому, что самый популярный ответ именно по Random, можно предположить, что вопросы, либо скучнейшие или… мы что то не догоняем, еще есть вариант — зачем мне это??)
sdev
16.06.2017 10:57Я думал что есть какой-то красиывый вариант, но, видимо, нет его. Нужно пергениерировать и писать циклы и условия.
shai_hulud
15.06.2017 17:51+3О очередной «i++ + i++» с собеседований запостили.
1) детали имплементации рантайма .NET, есть еще Mono, еще есть Mono под LLVM, есть еще отнсительно новый RyuJIT
2) О май гад, делегат это тип, в рот мне тапки. В каждой книге по С# об этом говорят.
3) Дети, а теперь давайте поработаем интерпретатором
4) З — Замыкания. Надеюсь автор знал правильный ответ.
5) Хрен проссышь что там тильда а не минус, пятерочка за крипто-операторы вроде бинарного комплемента (~) и отрицания (!).
6) GC.Collect() нет смысла вызывать никогда
Тогда вопрос автору, как сделать каст без боксинга и создания новых объектов в выражении:
// не меняя сигнатуру естественно public static int CastToInt<T>(T value) { return (?)value; }
AntonioGrande
15.06.2017 18:23А расскажите.
shai_hulud
15.06.2017 18:27Ближе к вечеру кину правильный ответ. Зачем портить удовольствие тому кто попытается его найти.
AntonioGrande
15.06.2017 18:42Вообще для того, чтобы писать хороший код, нужно понимать как работает фреймворк и среда исполнения. Это особенно важно, если вы hft трейдер, например. Пишете на с++, разбирайтесь как работает компилятор. Пишете под mono, читайте про mono. Это позволит понять, как ваш код оптимизировать. А подход «оно и так работает» мне лично непонятен. В конце концов вы же и заинтересованы в том, чтобы ваше приложение работало быстро и стабильно.
shai_hulud
15.06.2017 19:00+1>Вообще для того, чтобы писать хороший код, нужно понимать как работает фреймворк и среда исполнения.
Согласен. Но примеры в статье не про хороший код.
3) комбинация делегатов и использования результата вызова комбинации делегатов
4) замыкания, неявное создание объектов в куче
5) использования редкого оператора, где его визуально можно спутать с другим оператором (-)
6) Явные вызок GC.Collect() плохо пахнет даже с отговорками
vkushnir
15.06.2017 21:09Как человек, который уже третий год работает в финансовой сфере (не подвального уровня, а в компаниях международного уровня) могу с увереностью сказать, что гавнокода тут не меньше, чем в проектах других представителей энтерпрайз сектора, а может и больше. И поверьте мне на слово, трейдеров это не так сильно волнует, к сожалению… Самого это жутко бесит, но это реалии. Для обработки же большого кол-ва ордеров сейчас делается упор на количестве железа и масштабируемых системах, а не на «выжимке» из железа максимума… Им проще докупить сервер, чем платить специалисту за знание тонкостей работы железа, ОС и прочего. Касательно статьи, спасибо, большая часть и вправду может быть полезной, но не всё:
1) Возможно, интересно знать, но на практике есть этим знаниям применение? Не думаю… Меня как-то на собеседовании спросили, какой бит используется GC для маркировки обьекта после прохода по нему (при анализе, что нужно собрать). На кой, спрашивается, это нужно знать? Возможно, однажды, будет какой-то кейс где это может как-то пригодиться, но на такие случаи достаточно это один раз загуглить и забыть, а не держать такие знания в памяти…
4) Замыкание же. Обьезженая тема, как по мне. Главное понимать как это работает на практике, я думаю, а превращается ли это в отдельный клас в нутрях, важно ли это? Если да, то как часто?
Вообще, оглядываясь назад, на большинстве моих собеседований пытались узнать чего я не знаю, а не что мне известно. В большинстве ситуаций это просто было своего рода рычагом, чтобы снизить требуюмую изначально ЗП. Не так важно понимать, что человек знает, сколько что он может/умеет делать. Ведь не всегда наличие знаний подразумевает умение ними пользоваться… В этом контексте большинство нынешних собеседований не так показательны, к сожалению…
vkushnir
15.06.2017 20:16public static int CastToInt(T value)
{
return (int)(dynamic)value;
}
Скомпилируется и скастит, скажем, double в int. Для остального (типа стринга) пошлёт с эксешпеном :)darkdaskin
15.06.2017 21:33+2Боксинг всё равно будет. Компилятор генерирует такой код:
public static int CastToInt<T>(T v) { if (Test.<>o__0<T>.<>p__0 == null) { Test.<>o__0<T>.<>p__0 = CallSite<Func<CallSite, object, int>>.Create(Binder.Convert(CSharpBinderFlags.ConvertExplicit, typeof(int), typeof(Test))); } return Test.<>o__0<T>.<>p__0.Target(Test.<>o__0<T>.<>p__0, v); }
Где
Target
имеет типFunc<CallSite, object, int>
. А при первом вызове к тому же будут созданы новые объекты.
darkdaskin
15.06.2017 21:54+3Аналог
(int)(object)value
без боксинга будет таким:
public static int CastToInt2<T>(T v) { return __refvalue(__makeref(v), int); }
Оба варианта работают только когда
typeof(T) == typeof(int)
. Для непрямых приведений (например, из double к int) можно написать что-то вроде:
public static int CastToInt2<T>(T v) { if (typeof(T) == typeof(int)) return __refvalue(__makeref(v), int); else if (typeof(T) == typeof(double)) return (int) __refvalue(__makeref(v), double); // Similar conditions vor all possible casts else throw new InvalidCastException(); }
shai_hulud
15.06.2017 22:57Да, это правильный вариант "__refvalue(__makeref(v), int)".
Других вариантов без лишних аллокаций нет.darkdaskin
16.06.2017 14:28+2На самом деле
(int)(object)value
тоже может работать без выделения памяти, в зависимости от версии JIT. У меня получается такой код в x86 Release:
Для сравнения
(object)value
:
А вот вариант с
__refvalue(__makeref(v), int)
, тут кода уже больше и тоже есть внешний вызов:
Этот же код в x64 Release,
(int)(object)value
опять побеждает:
Не зря говорят, что преждевременная оптимизация — корень всех зол.
shai_hulud
16.06.2017 16:11+1Это сравнение того кода который сгенерит JIT компилятор.
Ему уже известно что вместо дженерика будет int, и кастить int в int он не будет.
Но, этот компилятор не будет, или в этой версии не будет. Это надежда на оптимизации компилятора. Не спорю что полезно о них знать, но оптимизации могут и не случиться, и приложение начнет засирать память.
Хотя наблюдение довольно интересное.
Spiceman
16.06.2017 09:49+1Бонус так решил:
static class A { static Random r = new Random(); static int c = 0; public static int rand3() { c = (c + 1) % 3; while (r.Next(2) == 0) c++; return c % 3; } }
Проверка:
> Enumerable.Range(0, 10000).Select(_ => A.rand3()).GroupBy(_ => _).Select(g => g.Count()).ToArray()
int[3] { 3365, 3300, 3335 }
GLeBaTi
16.06.2017 10:03+1Вопрос по третьему пункту. Зачем нужен MulticastDelegate, если у обычного делегата уже есть Cobine(+=)?
AntonioGrande
16.06.2017 10:08Цитата:
Любые типы делегатов — это потомки класса MulticastDelegate, от которого
они наследуют все поля, свойства и методы.
Класс System.MulticastDelegate является производным от класса System.Delegate,
который, в свою очередь, наследует от класса System.Object. Два класса делегатов
появились исторически, в то время как в FCL предполагался только один.
Aler
Спасибо за статью, полезно. Я бы результат ранд2 принял бы за бинарный рандом и ранд3 реализовал бы через сдвиги:
Aler
Хотя нет, извините, нельзя конечно же. Будет еще нежелательное 3 при двух единицах
kenoma
*удалил неправильный ответ