Комментарии
Лично в моей практике на собесах на миддла скорее спрашивают как работают внутренности каких-то вещей, которые очень активно используются на проекте, например как внутри устроен 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