
Комментарии
Лично в моей практике на собесах на миддла скорее спрашивают как работают внутренности каких-то вещей, которые очень активно используются на проекте, например как внутри устроен List и как его правильно использовать в проекте где постоянно туда-сюда тягаются данные в листах. Или просят предоставить решение какой-то абстрактной задачи, связанной с тем что придется делать. К примеру проработать процесс размещения заказа покупателем в интернет магазине с учетом всех возможных фейлов.
string особенный тип
- хранятся в heap как refernce type
- при присвоении (передаче в метод) и сравнении ведут себя как value type
Ничего не сказано о том, что строки в .NET неизменяемы.
при присвоении (передаче в метод) и сравнении ведут себя как value type
А вот это что значит? Они ж не создают копии при передачи в метод. По-моему, неправда написана.
Строки похожи по поведению на типы значений из-за того, что они неизменяемы.
Теперь стало ещё хуже.
Как связана изменяемость с value type?
Ответьте на вопрос про изменяемость.
проверка равенства строк отличается от других ссылочных типов
Ничто не мешает и для других ссылочных типов перегрузить operator == и получить ту же логику.
str = «str2»;эквивалентно «obj1 = obj2;», т.е. вы в переменную положили ссылку на другой объект.
Ваши выводы в примере говорят о том, что вы не знаете ни как работает String, ни как работает передача параметров в метод!
Вот вам альтернативный пример. Вы понимаете, почему вывод именно такой?
public class MyClass
{
public string Str;
}
static void Main(string[] args)
{
string s1 = "str";
MyClass c1 = new MyClass { Str = s1 };
var tmp = c1;
void myMethod(MyClass my)
{
Console.WriteLine("ReferenceEquals('str', my.Str) => " + Object.ReferenceEquals("str", my.Str));
// ==> true
Console.WriteLine("Object.ReferenceEquals(c1, my) => " + Object.ReferenceEquals(c1, my));
// ==> true
my = new MyClass { Str = "str2" };
Console.WriteLine("ReferenceEquals('str2', my.Str) => " + Object.ReferenceEquals("str2", my.Str));
// ==> true
Console.WriteLine("Object.ReferenceEquals(c1, my) => " + Object.ReferenceEquals(c1, my));
// ==> false
}
myMethod(c1);
Console.WriteLine("Object.ReferenceEquals(c1, tmp) => " + Object.ReferenceEquals(c1, tmp));
// ==> true
Console.WriteLine(s1);
}
str = «str2»;
эквивалентно «obj1 = obj2;»,
Позор, посыпаю голову пеплом. Хотелось пример демонстрирующий immutable строк.
вот эти оба пункта неверны:
из-за того, что строки immutable и при сравнении сравниваются их значения (стандартное поведение reference type сравнивать ссылки) строки по поведению похожи на value type
из-за immutable при склеивании длинных строк нужно использовать StringBuilder
Тип особенный, но вы совсем не то про него пишете!
Прежде чем делать дальнейшие изменения этого раздела, разберитесь, почему в мой пример работает именно так, как описано.
class MyString
{
private readonly char[] _value;
public MyString(IEnumerable<char> value)
{
_value = value.ToArray();
}
public MyString Add(MyString str) => new MyString(_value.Concat(str._value));
public MyString Reverse() => new MyString(_value.Reverse());
public override string ToString() => new string(_value);
}
Всё-таки Derived, а не Delivered.
// НЕ КОМПИЛИРУЕТСЯ, List - конвариантенПравильно будет: «контравариантен».
List инвариантен, контравариантность — это другое (делегаты).
https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance
Еще интересно бывает спросить программиста, использовал ли он новые тъюплы типа (bool a, string b), или SelectMany() из linq,? и ??, понимает ли как использовать замыкания. Практика использования функциональщины может говорить об опыте в создании сложных алгоритмов.
Вот дали вам стандартную библиотеку, отлаженную, проверенную. Нет, каждый пытается изобрести что-то свое. И причина вроде понятна — там меньше памяти, там быстрее выполняется. Страшно то, когда эти кастомные варианты начинают использовать не разобравшись, а нафига это было сделано, а просто доверившись «на 200%» быстрее. И как итог памяти +200% время +200%
использовал ли он новые тъюплы типа (bool a, string b),? и ??
Постоянно.
И я очень слабо понимаю как можно 10 лет кодить на шарпе и даже случайно не напороться на поколения GC, обрезание коллстэка и финалайзеры. Если только формошлепством бесконечным заниматься каким-нибудь.
Блин, 10 лет c# и только в первый раз прочитал про «конвариантность», финалайзер, обрезание CallStack и уровни сборщика мусора. Еще каждый раз лезу в интернет, когда надо написать делегат или не самый стандартный switch.
16 лет на C#. Ковариантность/контрвариантность периодически встречаю, Finalizer / CallStack — никогда, уровни редко (в основном, когда нужно ковырять потребление памяти).
Еще интересно бывает спросить программиста, использовал ли он новые тъюплы типа (bool a, string b), или SelectMany() из linq,? и ??, понимает ли как использовать замыкания.
«Тьюплы» (ещё когда они были просто Tuple), linq, "?", "??", замыкания — вообще непрерывно в коде используются. Всё на них пишется.
string особенный тип
Бред какой-то. Самый обычный тип, просто с перегрузкой оператора ==.
Единственная особенность строк — это string interning.
Строка действительно особый тип. Приведу цитату из книги Конрада Кокосы:
Строки обрабатываются в .NET особым образом, поскольку по умолчанию они неизменяемы. В отличие от неуправляемых языков типа C или C++, мы не можем изменить значение строки, после того как она создана.
…
Неизменяемость строк приводит к большой неразберихе при первом знакомстве с языком C#.
Я могу создать свой собственный класс, который не будет отличаться по поведению от System.String. Так в чём заключается утверждение "строки обрабатываются в .NET особым образом"?
А как вы ее собираетесь изменять, если уж на то пошло? У нее нет внутренних свойств или полей, в которые можно было бы что-то записать, а ее методы совершенно явно возвращают новое значение, т.е. не имеют типа void, чтобы хоть как-то намекать на изменяемость.
ref и out
ref и out позволяют внутри метода использовать new и для class и для struct
Согласно документации их основное назначение:
parameter must be initialized before it is passed. This differs from out parameters, whose arguments do not have to be explicitly initialized before they are passed.А куда дели «in parameter modifier» и «Reference return values (or ref returns)», «Ref locals»?
Залезли в такую интересную тему ссылок указателей и так поверхностно к ней отнеслись?
Finalizer (destructor) ~
вызывается когда garbage collector доберется до объекта
Но… «есть нюанс». И опять же не расписан…
выявление есть ссылки на объект (объект используется) или его можно удалить — это трудозатратная задача
Опять же — удалить объект не сложно, а «дорого» (с точки зрения ресурса CPU).
Основная «цена» платится за переупорядочивание памяти\устранения дефрагментации и корректировки ссылок на «живые» объекты.
Начитаются таких «шпаргалок» и удивляются результатам на собеседованиях.
p.s.
В общем «тема сисек не раскрыта»…
(структуры с помощью == сравнивать нельзя)
Неужели?
public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<int>, IEquatable<int>Beginning with C# 7.3, the == and != operators are supported by C# tuples. For more information, see the Tuple equality section of the Tuple types article.
давно перестал удивляться на собеседованиях… грустно…
«don't support the == operator by default» не тождественно «структуры с помощью == сравнивать нельзя», да и «User-defined struct» это не все struct.
Не более.
Знание документации «наизусть» с учетом того, что она обновляется быстрее, чем успевают ее прочитать и применить — не наш метод.
Зная некоторые принципы, можно не беспокоиться о многих фактах :)
value type (пример int, struct, ссылки на инстансы reference type) хранятся в быстром stack
Это неверно. Простейший контрпример:
class RefType
{
int Value;
}Value будет храниться вместе со всем объектом, в куче.
Затем, замыкания, async-функции, генераторы, все они могут оправить value typed-значение в кучу.
Затем, само наличие стека — это подробность реализации текущей Microsoft CLR, она не гарантирована по стандарту.
Немного по теме: ответ на SO и статья Эрика Липперта, обсуждаемая в этом ответе.
Кстати, и reference types не прибиты гвоздями к куче. Вот тут их от кучи отвязывают.
Затем, само наличие стека — это подробность реализации текущей Microsoft CLR, она не гарантирована по стандарту.
Интересно, спасибо.
Это неверно. Простейший контрпример:
Тут противоречия не вижу, в контрпримере class — в заметке отметил что class в куче (имеется ввиду все его value тоже).
Но в вашем тексте написано безусловно: «value type (...) хранятся в быстром stack», а не «локальные переменные значимых типов хранятся на стеке, за исключением (тут перечисление)».
То, что поля хранятся там же, где и весь объект, вовсе не очевидно: например, если объект значимого, а поле ссылочного типа, то они хранятся не вместе.
reference type (пример class, interface) хранятся в большой heap
value type (пример int, struct, ссылки на инстансы reference type) хранятся в быстром stack
при присвоении (передачи в метод) value type копируются, reference type передаются по ссылке (см. ниже раздел struct)
ну тысячу раз же обсасывалось что это не так…
1. куча и стэк сами по себе ни быстрые ни медленные (**размещение** на куче медленнее, а доступ уже зависит от эффектов локальности)
1.1 > большой heap: можно сделать мелкую кучу и огромный стэк
2. значимые типы регулярно можно встретить в куче
3. ссылочный объект, немного постаравшись, можно разместить на стэке (была статья на хабре и несколько импортных)
3. при вызове методов все типы передаются по-умолчанию по значению (для ссылочного типа передаётся значение ссылки), для «передачи по ссылке» есть ключевые слова ref и out, они могут применяться как к ссылочным так и к значимым типам (тип и способ передачи это ортогональные понятия)
для «передачи по ссылке» есть ключевые слова ref и out, они могут применяться как к ссылочным так и к значимым типам
В разделе «ref и out» действительно не хватает пары пунктов (хотя и отмечено что ref, out применяются к ссылочным и значимым типам), допишу.
По остальным пунктам, по поводу того что можно сделать (мелкую кучу, разместить ссылочный тип в стеке) — это все таки не темы для обсуждения на собеседовании для джуна/миддла. Выше в комментариях указали что «наличие стека — это подробность реализации текущей Microsoft CLR, она не гарантирована по стандарту». Да, это интересные темы, полезно об этом знать — спасибо что указали.
1. не надо путать singleton и lazy initialization (обычно это всё кидают в одну кучу)
2. в c# уже давно есть и для одного и для другого готовые конструкции, вручную городить лок, дабл-чек и барьер не надо
вручную городить лок, дабл-чек и барьер не надо
Хорошее дополнение, на собеседовании полезно сказать о встроенных возможностях. Но и реализацию singleton, т.к. это часто спрашивают на собеседованиях, нужно понимать.
То же и с «сортировкой пузырьком» — никто её сам не пишет, но на собеседованиях спрашивают.
нужно ещё понмать, какая из них корректная, большинство из них, включая ту, которая написана в википедии, будут работать некорректно в зависимости от нагрузки.
P.S.: пардон, проверил википедию, сейчас в статье Singleton реализация верная:
public static Singleton Instance { get; } = new Singleton();раньше там был код из статьи про Double Check Locking без барьера, который работал или медленно или неправильно.
> readonly — установить значение можно только до компиляции или в конструкторе
я даже не буду ничего говорить.
эта статья — какой-то один большой фейспалм…
За «деструктор» в дотнете принято бить по морде. Снисхождение можно проявить только к заядлым плюсовикам. (но им надо объяснить что то, что они пишут как ~Foo() тут называется Dispose())
> вызывается когда garbage collector доберется до объекта
а вот и нет, гуглите SuppressFinalize
За «деструктор» в дотнете принято бить по морде
Да, в заметке об этом явно написано:
зачем может пригодиться переопределять finalizer: предпочтительней реализовать IDisposable
более того, указано что GC вызовет финализатор когда доберётся до объекта, но по факту он может добраться до объекта и __не__ вызвать финализатор.
P.S.:
> зачем может пригодиться переопределять finalizer: предпочтительней реализовать IDisposable
так каноничный disposable pattern (если не считать новых disposable struct) как раз включает в себя правильный финализатор. По морде бьют не за ~Foo() а за называние «финализатора» «деструктором»
По морде бьют не за ~Foo() а за называние «финализатора» «деструктором»
Тогда Вам в microsoft-е надо порядок навести
docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/classes-and-structs/destructors
Finalizers (which are also called destructors) are used to
«Бить морды» за расхождения в терминологии — не наш метод.
Кроме как в этом дополнении слово «деструктор» там сейчас встречается ровно 0 раз.
Человека который имеет представление о C++, слово "деструктор" будет очень сильно сбивать. Не надо его использовать применительно к C#.
> вызывается только автоматически средой .Net, нельзя вызвать самостоятельно
myObj.GetType().GetMethod("Finalize",
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.DeclaredOnly)
.Invoke(myObj, null);
Наряду с ref, out сегодня есть ещё и in.
Уровень описания в статье — junior.
Даже middle должен бы знать, что string ни разу не похож на value type. У этого типа есть две ключевые особенности: immutable (любое изменение порождает новый экземпляр) и оптимизация компилятором (если в коде встречается несколько раз один строковый литерал, после компиляции это будет один и тот же объект)
Так же middle должен понимать, для чего используются публичные методы object, и когда и для чего требуется их переопределять. И что GetHashCode надо переопределять совместно с Equals во избежание разногласий (но не для ускорения).
Список можно продолжить. Но я обычно задаю вопросы вразрез, кандидат сам расскажет, что понимает и шпаргалки не помогут. Например: когда (в каких случаях) ValueType хранится в куче? Занимает ли ReferenceType место на стеке? Всегда ли вызывается Dispose? С какими паттернами разработчик сталкивался на предыдущем проекте (к примеру, типичный asp.net-разработчик сталкивается как минимум с пятью паттернами, но чаще всего их в упор не видит) и т.д.
string ни разу не похож на value type
Действительно, про immutable и StringBuilder надо добавить (в комментариях несколько раз на это указывали).
Видимо фраза из заметки
при присвоении (передаче в метод) и сравнении ведут себя как value type
не совсем точная, имелось ввиду «не смотря на то, что значение string хранится в куче, поведение string при сравнении (передаче в метод) совсем не как у reference type и больше похоже на value type: разные переменные строк с одинаковым значением при сравнении == дадут true, несмотря на то что ссылки у них разные ....».
Во-вторых, фраза
поведение string при сравнении (передаче в метод) совсем не как у reference type и больше похоже на value type:не корректна. Не надо путать новичков, они потом это приносят на собеседовании и очень портят впечатление.
Я не единожды писал классы, которые
разные переменные с одинаковым значением при сравнении == дадут true, несмотря на то что ссылки у них разные, и даже делал immutable классы. Ничто из это не имеет отношения ни к value type, ни к строкам.
В разделе "Порядок инициализации" есть неточность в описании порядка инициализации статических полей и вызова статических конструкторов. Среда гарантирует, что статические поля будут инициализированы, и статический конструктор будет вызван до первого использования класса, отношение наследования никак на порядок статической инициализации не влияет. Например, если в статическом конструкторе наследника как-то используется базовый класс (статические поля или создаётся экземпляр), то инициализация статических полей и вызов статического конструктора базового класса произойдёт до аналогичных действий для наследника.
value type (пример int, struct, ссылки на инстансы reference type) хранятся в быстром stack
Уже говорили про это? Думаю, что если все еще не изменено, значит нет.
value type не может быть в куче? И я не про boxing. А просто про ссылочный тип, поле которого значимого типа, массивы и тд. Ну и что основная разница между ними, это не то, где они хранятся, а как и как передаются
Шпаргалка для собеседования .Net