Шпаргалка для собеседования .Net

    Ниже не учебник, а только шпаргалка для разработчиков уже знакомых с основами C# .Net.

    Шпаргалка содержит только вопросы "на базу". Вопросы вида "как бы вы спроектировали ...", "какие слои приложения ...", в шпаргалку не входят. Как отметили в комментариях, вопросы скорее для джуна, тем не менее их задают и на собеседованиях миддлов.

    Форматирование кода

    В примерах, для краткости, открывающая скобочка { не на новой строке. Интервьюер может быть смущен, т.к. в C# принято ставить { с новой строки. Поэтому на собеседовании лучше использовать общепринятое форматирование.

    stack и heap, value type и reference type

    • reference type (пример class, interface) хранятся в большой heap

    • value type (пример int, struct, ссылки на инстансы reference type) хранятся в быстром stack

    • при присвоении (передачи в метод) value type копируются, reference type передаются по ссылке (см. ниже раздел struct)

    struct

    • value type => при присвоении (передачи в метод) все поля и свойства копируются, не может быть null

    • нет наследования

    • поддерживает интерфейсы

    • если есть конструктор, в нем должны устанавливаться все поля и свойства

    interface IMyInterface {
        int Property { get; set; }
    }
    
    struct MyStruc : IMyInterface {
        public int Field;
        public int Property { get; set; }
    }
    
    class Program {
        static void Main(string[] args) {
            var ms = new MyStruc { 
                Field = 1,
                Property = 2
            };
          	// при передаче в метод value type копируется,
          	// поэтому в методе будет другая переменная
            TryChange(ms);
            Console.WriteLine(ms.Property);
            // ==> ms.Property = 2;
    
          	// тут происходит boxing (см ниже)
            IMyInterface msInterface = new MyStruc {
                Field = 1,
                Property = 2
            };
          	// поэтому в метод передается уже object (reference type)
          	// внутри метода будет не другая переменная, а ссылка на msInterface
            TryChange(msInterface);
            Console.WriteLine(msInterface.Property);
            // ==> ms.Property = 3;
        }
    
        static void TryChange(IMyInterface myStruc) {
            myStruc.Property = 3;
        }
    }

    DateTime это struct, поэтому проверять поля типа DateTime на null бессмысленно:

    class MyClass {
        public DateTime Date { get; set; }
    }
    
    var mc = new MyClass();
    // всегда false, 
    // т.к. DateTime это struct (value type) не может быть null
    var isDate = mc.Date == null;

    boxing / unboxing

    // boxing (value type, stack -> object, heap)
    int i = 123;
    object o = i;
    
    // unboxing (object, heap -> value type, stack)
    object o = 123;
    var i = (int)o;
    boxing
    boxing
    // пример boxing
    int i = 123;
    object o = i;
    i = 456;
    // результат ==> т.к. i, o хранятся в разных ячейках памяти
    //  i = 456
    //  o = 123

    Зачем это нужно

    // При приведении структуры к интерфейсу происходит boxing
    IMyInterface myInterface = new MyStruct(2);
    
    // boxing i
    int i = 2;
    string s = "str" + i;
    // т.к. это String.Concat(object? arg0, object? arg1)
    
    // unboxing, т.к. Session Dictionary<string, object>
    int i = (int)Session["key"];

    string особенный тип

    • хранятся в heap как reference type, передаются в метод как reference type

    string s1 = "str";
    void methodStr(string str) {
        Console.WriteLine(Object.ReferenceEquals(s1, str));
        // ==> true
        // string reference type, str и s1 указывают на один объект
    }
    methodStr(s1);
    
    
    int i1 = 1;
    void methodInt(int num) {
        Console.WriteLine(Object.ReferenceEquals(i1, num));
        // ==> false
        // int value type, i1 и num это разные объекты
    }
    methodInt(i1);
    • строки не изменяемы (immutable) - каждое изменение создает новый объект

    • из-за того, что строки immutable и при сравнении сравниваются их значения (стандартное поведение reference type сравнивать ссылки) строки по поведению похожи на value type

    • из-за immutable при склеивании длинных строк нужно использовать StringBuilder

    const vs readonly

    • const - значение подставляется при компиляции => установить можно только до компиляции

    • readonly - установить значение можно только до компиляции или в конструкторе

    class MyClass {
        public const string Const = "some1";
        public readonly string Field = "some2";
    }
    
    var cl = new MyClass();
    Console.WriteLine(MyClass.Const);
    Console.WriteLine(cl.Field);
    Программа после компиляции. Компилятор подставил значение const.
    Программа после компиляции. Компилятор подставил значение const.

    Фокус-покус с подкладыванием dll библиотеки, без перекомпиляции основного проекта:

    Значение const в библиотеке отличается от используемого в основном проекте.
    Значение const в библиотеке отличается от используемого в основном проекте.

    ref и out

    • ref и out позволяют передавать в метод ссылки на объекты, и для value type и для reference type

    static void Main(string[] args) {
        int i = 1;
    
        Change1(i);
        // передаем значение i
        Console.WriteLine(i);
        // ==> i = 1
    
        // передаем ссылку на i
        Change2(ref i);
        Console.WriteLine(i);
        // ==> i = 2
    }
    
    static void Change1(int num) {
        num = 2;
    }
    
    static void Change2(ref int num) {
        num = 2;
    }
    • ref и out позволяют внутри метода использовать new и для class и для struct

    • out тоже что ref, только говорит о том что, метод обязательно пересоздаст переменную

    struct MyStruc {
        public int Field;
    }
    
    class Program {
        static void Main(string[] args) {
            var ms = new MyStruc { Field = 1 };
            createNew(ms);
            Console.WriteLine(ms.Field);
            // ==> ms.Field = 1
    
            var ms2 = new MyStruc { Field = 1 };
            createNew2(ref ms2);
            Console.WriteLine(ms2.Field);
          	// ==> ms2.Field = 2
        }
    
        static void createNew(MyStruc myStruc) {
            myStruc = new MyStruc { Field = 2 };
        }
    
        static void createNew2(ref MyStruc myStruc) {
            myStruc = new MyStruc { Field = 2 };
        }
    
        static void createNew3(out MyStruc myStruc) {
            // ошибка компиляции, 
            // нужно обязательно использовать myStruc = new
        }
    }

    Ковариантность

    Термин встречается только при обсуждении generic-ов.

    interface IAnimal { }
    class Cat : IAnimal {
        public void Meow() { }
    }
    class Dog : IAnimal {
        public void Woof() { }
    }
    
    
    // НЕ КОМПИЛИРУЕТСЯ, List - инвариантен
    // не компилируется, потому что у List есть метод Add,
    // который приводит к коллизиям (пример коллизии см. ниже)
    List<IAnimal> animals = new List<Cat>();
    
    // компилируется, IEnumerable - ковариантен
    // у IEnumerable нет методов приводящих к коллизиям
    IEnumerable<IAnimal> lst = new List<Cat>();

    К каким коллизиям приводит метод Add в List:

    // это компилируется и работает
    
    List<Cat> cats = new List<Cat>();
    cats.Add(new Cat());
    List<Cat> animals = cats;
    animals.Add(new Cat());
    
    foreach (var cat in cats) {
        cat.Meow(); // в cats 2 кошки
    }
    
    
    // это НЕ КОМПИЛИРУЕТСЯ
    
    List<Cat> cats = new List<Cat>();
    cats.Add(new Cat());
    List<IAnimal> animals = cats;
    animals.Add(new Dog()); // это не порядок, потому что:
    
    // перебираем
    foreach (var cat in cats) {
        cat.Meow(); // в cats 1 кошка и 1 собака, у собаки нет метода Meow()
    }

    Публичные методы Object

    • ToString

    • GetType

    • Equals

    • GetHashCode

    Про ToString и GetType спрашивать нечего.

    Equals и GetHashCode нужны для сравнения объектов в коллекциях, linq, многопоточности. В основном переопределяют для ускорения, т.к. встроенные методы должны обеспечивать все многообразие платформы .Net. Разработчик может знать как быстрее посчитать hash для своих конкретных объектов.

    Если объекты возвращают одинаковый GetHashCode не значит что они равны.

    События, делегаты

    class MyClass {
        public event Action<string> Evt;
        public void FireEvt() {
            if (Evt != null)
                Evt("hello");
    
            // Evt("hello") - на самом деле перебор обработчиков
            // можно сделать тоже самое вручную
            //foreach (var ev in Evt.GetInvocationList())
            //    ev.DynamicInvoke("hello");
        }
    
        public void ClearEvt() {
            // отписать всех подписчиков можно только внутри MyClass
            Evt = null;
        }
    }
    
    
    var cl = new MyClass();
    
    // подписаться на событие
    cl.Evt += (msg) => Console.WriteLine($"1 {msg}");
    cl.Evt += (msg) => Console.WriteLine($"2 {msg}");
    
    // подписаться и отписаться
    Action<string> handler = (msg) => Console.WriteLine($"3 {msg}");
    cl.Evt += handler;
    cl.Evt -= handler;
    
    cl.FireEvt();
    // ==> 
    //  1 hello
    //  2 hello
    
    
    // это НЕ КОМПИЛИРУЕТСЯ
    // на событие можно подписаться "+=" или описаться "-="
    // отписать всех подписчиков можно только внутри MyClass
    cl.Evt = null;

    Finalizer ~

    • вызывается когда garbage collector доберется до объекта. Можно указать GC не вызывать finalizer для определенного instance - GC.SuppressFinalize.

    • вызывается только автоматически средой .Net, нельзя вызвать самостоятельно (если очень хочется можно вызвать с помощью reflection).

    • нельзя определить для struct

    • зачем может пригодиться переопределять finalizer: предпочтительней реализовать IDisposable. Встречаются идеи дублировать логику Dispose в finalizer, на случай если клиентский код не вызывал Dispose. Или вставлять в finalizer логирование времени жизни объекта для отладки.

    throw vs "throw ex"

    try {
        ...
    } catch (Exception ex) {
    
        // это лучше, т.к. не обрезается CallStack
        throw;
    
        // обрезает CallStack
        throw ex;
    }

    Garbage collector

    Коротко. heap большая, но все же имеет ограниченный размер, нужно удалять неиспользуемые объекты. Этим занимается Garbage collector. Деление объектов на поколения нужно для следующего:

    • выявление есть ссылки на объект (объект используется) или его можно удалить - это трудозатратная задача

    • поэтому имеет смысл делать это не для всех объектов в heap

    • те объекты которые создали недавно (Generation 0) - вероятно это объекты используемые внутри метода, при выходе из метода они не нужны их можно удалить. Поэтому вначале искать объекты на удаление нужно в поколении Generation 0.

    • те объекты которые пережили сборку мусора - называют объектами Generation 1.

    • если Generation 0 почистили, а памяти не хватает. Приходится искать ненужные объекты среди тех которые пережили сборку - в Generation 1.

    • если все равно нужно еще чистить, ищем среди тех кто пережили 2 сборки мусора - в Generation 2.

    Порядок инициализации

    • Derived.Static.Fields

    • Derived.Static.Constructor

    • Derived.Instance.Fields

    • Base.Static.Fields

    • Base.Static.Constructor

    • Base.Instance.Fields

    • Base.Instance.Constructor

    • Derived.Instance.Constructor

    class Parent {
        public Parent() {
          	// нельзя вызывать virtual в конструкторе
          	// конструктор базового класса вызывается
          	// раньше конструктора наследника
            DoSomething();
        }
        protected virtual void DoSomething() {
        }
    }
    
    class Child : Parent {
        private string foo;
        public Child() {
            foo = "HELLO";
        }
        protected override void DoSomething() {
            Console.WriteLine(foo.ToLower()); //NullReferenceException
        }
    }

    Наследование в клиентском коде часто порождает излишнюю сложность (по мои наблюдениям, мое частное мнение), поэтому его нужно избегать. Для повторного использования кода лучше применить агрегацию (или, что хуже, композицию) - см. Наследование vs Композиция vs Агрегация.

    ООП

    • Абстракция - отделение идеи от реализации

    • Полиморфизм - реализация идеи разными способами

    • Наследование - повторное использование кода лучше реализовать с помощью агрегации или, что хуже, композиции)

    • Инкапсуляция - приватные методы

    SOLID

    • Single responsibility - объект, метод должны заниматься только одним своим делом, в противоположность антипатерну God-object

    • Open closed principle - для добавления новых функций не должно требоваться изменять существующий код

    • Liskov substitution - использовать базовый класс не зная о реализации наследника

    • Interface segregation principle - не раздувать интерфейсы

    • Dependency inversion principle - вначале интерфейсы, потом реализация, но не наоборот

    Паттерны

    Делятся на 3 типа

    • порождающие (пример: фабрика)

    • структурные (пример: декоратор)

    • поведенческие (пример: цепочка обязанностей)

    Что еще спрашивают

    • IDisposable, try, catch, finally

    • Напишите singleton (не забудьте про потокобезопасность и lock)

    • домены приложений

    • синхронизации потоков (mutex, semaphore и т.п.)

    • Позитивные/негативные блокировки. Например: первый пользователь открыл форму на редактирование. Пока первый правит, второй успел внести изменения. Первый нажимает сохранить. Хорошо это или плохо? Какие варианты решения проблемы (если она есть)?

    • SQL запросы, особенно с HAVING

    • уровни изоляции БД

    Литература

    Stack and heap – .NET data structures

    Boxing and Unboxing (C# Programming Guide)

    Built-in reference types (C# reference)

    Covariance and contravariance in generics

    C# variance problem: Assigning List as List

    Finalizers (C# Programming Guide)

    Destructors in real world applications?

    Virtual member call in a constructor

    Наследование vs Композиция vs Агрегация

    Fundamentals of garbage collection

    Комментарии 82

      +4
      Как по мне так большая часть вопросов уровня джуна, а не миддла.

      Лично в моей практике на собесах на миддла скорее спрашивают как работают внутренности каких-то вещей, которые очень активно используются на проекте, например как внутри устроен List и как его правильно использовать в проекте где постоянно туда-сюда тягаются данные в листах. Или просят предоставить решение какой-то абстрактной задачи, связанной с тем что придется делать. К примеру проработать процесс размещения заказа покупателем в интернет магазине с учетом всех возможных фейлов.
        +4

        Я собеседовал огромное количество кандидатов на senior, которые не знали и половины этих тем.

          0
          Хорошие сеньеры-помидоры, ничего не скажешь.
          0
          Вы даже не представляете… Больше половины соискателей на Middle/Senior отсеиваются уже на этих вопросах и до решения задач просто не добираются.
          +2
          string особенный тип
          • хранятся в heap как refernce type
          • при присвоении (передаче в метод) и сравнении ведут себя как value type

          Ничего не сказано о том, что строки в .NET неизменяемы.



            0
            Действительно, как же забыл по StringBuilder отметить
            0
            при присвоении (передаче в метод) и сравнении ведут себя как value type

            А вот это что значит? Они ж не создают копии при передачи в метод. По-моему, неправда написана.

              +1
              Просто здесь не совсем правильно подан материал. Строки похожи по поведению на типы значений из-за того, что они неизменяемы.

                0
                Строки похожи по поведению на типы значений из-за того, что они неизменяемы.

                Теперь стало ещё хуже.
                Как связана изменяемость с value type?

                  0
                  Например, проверка равенства строк отличается от других ссылочных типов: если ссылки не равны, то производится дополнительная проверка при которой сравниваются значения строк. Сначала сравнивают длину строк, если они совпадают, то производится посимвольное сравнение.
                    –1

                    Ответьте на вопрос про изменяемость.


                    проверка равенства строк отличается от других ссылочных типов

                    Ничто не мешает и для других ссылочных типов перегрузить operator == и получить ту же логику.

                      0
                      Переписал раздел «string особенный тип»
                        +1
                        У вас не получилось.
                        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);
                        }
                        
                          0
                          str = «str2»;
                          эквивалентно «obj1 = obj2;»,

                          Позор, посыпаю голову пеплом. Хотелось пример демонстрирующий immutable строк.
                            0
                            Но и оговорку про поведение «как value type» уберите.
                              +1
                              да, кстати, стало не лучше.
                              вот эти оба пункта неверны:
                              из-за того, что строки immutable и при сравнении сравниваются их значения (стандартное поведение reference type сравнивать ссылки) строки по поведению похожи на value type

                              из-за immutable при склеивании длинных строк нужно использовать StringBuilder


                              Тип особенный, но вы совсем не то про него пишете!

                              Прежде чем делать дальнейшие изменения этого раздела, разберитесь, почему в мой пример работает именно так, как описано.
                  0
                  Вообще строка это просто класс String у которого все его методы не изменяют его внутреннего состояния а только возвращают новый инстанс этого класса. Вы собственно можете сами такой же иммутабельный класс как String написать. Например так (Так для справки — всегда валидируйте входные параметры в публиных методов в реальном коде. Тут просто примитивный пример):
                      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);
                      }
                  
                +1
                Про GetType спрашивать нечего

                Как это нечего? А зачем может понадобиться получение типа объекта? А один-два примера того, что можно делать с помощью рефлексии типов? А напишите простенький сериализатор для простенького самописного класса?
                  0

                  Всё-таки Derived, а не Delivered.

                    0
                    Спасибо, поправил
                      0
                      Тогда уж и
                      stack и heap, value type и rerence type
                    0
                    Вот бы еще к каждому вопросу и ссылку на хорошую статью по теме… как на « Наследование vs Композиция vs Агрегация». Было бы вообще замечательно.
                      0
                      Согласен «Наследование vs Композиция vs Агрегация» — отличная статья. На тему stack, heap мне вот эта нравилась — www.codejourney.net/2018/08/net-internals-02-stack-and-heap-net-data-structures (где то и перевод был).
                        0
                        Спасибо. Перевод не особо нужен. Люблю в оригинале читать.
                      –1
                      // НЕ КОМПИЛИРУЕТСЯ, List - конвариантен

                      Правильно будет: «контравариантен».

                      –2
                      Блин, 10 лет c# и только в первый раз прочитал про «конвариантность», финалайзер, обрезание CallStack и уровни сборщика мусора. Еще каждый раз лезу в интернет, когда надо написать делегат или не самый стандартный switch.
                      Еще интересно бывает спросить программиста, использовал ли он новые тъюплы типа (bool a, string b), или SelectMany() из linq,? и ??, понимает ли как использовать замыкания. Практика использования функциональщины может говорить об опыте в создании сложных алгоритмов.
                        0
                        Блин, лет 20 как уже не программист (j2me), а до сих пор поражает любовь к велосипедам.
                        Вот дали вам стандартную библиотеку, отлаженную, проверенную. Нет, каждый пытается изобрести что-то свое. И причина вроде понятна — там меньше памяти, там быстрее выполняется. Страшно то, когда эти кастомные варианты начинают использовать не разобравшись, а нафига это было сделано, а просто доверившись «на 200%» быстрее. И как итог памяти +200% время +200%
                          0
                          А если это и есть ваша работа писать те самые библиотеки?
                          –1
                          использовал ли он новые тъюплы типа (bool a, string b),? и ??

                          Постоянно.

                          И я очень слабо понимаю как можно 10 лет кодить на шарпе и даже случайно не напороться на поколения GC, обрезание коллстэка и финалайзеры. Если только формошлепством бесконечным заниматься каким-нибудь.
                            0
                            Для меня тоже загадка почему так получается. Моя теория — значение знаний, компетентности и вообще мозга преувеличено. При наличии должного креатива, воображения и энтузиазма можно создать коммерчески успешный продукт с прекрасными перспективами развития, пользуясь только самыми базовыми знаниями. Важно сделать это «вчера», совершенно самостоятельно, без ТЗ, без любого контроля сверху, полностью взять проект под контроль, перетянуть на себя все ключевые модули, особенно те, от которых у других начинается апатия, еще нужно затянуть в эту архитектуру кучу разработчиков (которые матерясь про себя начнут вносить в это все профессиональные решения). Тогда прежде чем у тебя появляется время на то, чтобы разобраться с GC к тебе уже бегут со следующей задачей и требуют, чтобы ты провернул этот номер снова. И им вообще все равно что ты ужасно некомпетентен и местами туп как дерево. При этом с ужасом ощущая, что формально завалил бы собеседование на джуна постоянно выполняешь работу тимлида. Возможно, если бы я продавал себя как крутой разработчик c#, то изучил бы все нюансы, но нужно понимать, что жизнь многообразна и карьера у всех развивается по разному. Здесь есть минусы: энтузиазм и креативность — вещи которые трудно сохранять долго. Но пока с возрастом из всего перечисленного выше набора я утратил только способность делать «вчера», но вместо нее пока работает опыт и портфолио.
                            +1
                            Блин, 10 лет c# и только в первый раз прочитал про «конвариантность», финалайзер, обрезание CallStack и уровни сборщика мусора. Еще каждый раз лезу в интернет, когда надо написать делегат или не самый стандартный switch.

                            16 лет на C#. Ковариантность/контрвариантность периодически встречаю, Finalizer / CallStack — никогда, уровни редко (в основном, когда нужно ковырять потребление памяти).
                            Еще интересно бывает спросить программиста, использовал ли он новые тъюплы типа (bool a, string b), или SelectMany() из linq,? и ??, понимает ли как использовать замыкания.

                            «Тьюплы» (ещё когда они были просто Tuple), linq, "?", "??", замыкания — вообще непрерывно в коде используются. Всё на них пишется.
                              0
                              «Тьюплы» (ещё когда они были просто Tuple), linq, "?", "??", замыкания — вообще непрерывно в коде используются. Всё на них пишется.
                              Да, поэтому в шпаргалку в статье это можно добавить, раз уж там проходим по всем основам.
                            +1
                            string особенный тип

                            Бред какой-то. Самый обычный тип, просто с перегрузкой оператора ==.
                            Единственная особенность строк — это string interning.

                              –2

                              Строка действительно особый тип. Приведу цитату из книги Конрада Кокосы:


                              Строки обрабатываются в .NET особым образом, поскольку по умолчанию они неизменяемы. В отличие от неуправляемых языков типа C или C++, мы не можем изменить значение строки, после того как она создана.

                              Неизменяемость строк приводит к большой неразберихе при первом знакомстве с языком C#.
                                +1

                                Я могу создать свой собственный класс, который не будет отличаться по поведению от System.String. Так в чём заключается утверждение "строки обрабатываются в .NET особым образом"?

                                  0

                                  А как вы ее собираетесь изменять, если уж на то пошло? У нее нет внутренних свойств или полей, в которые можно было бы что-то записать, а ее методы совершенно явно возвращают новое значение, т.е. не имеют типа void, чтобы хоть как-то намекать на изменяемость.

                                +2
                                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.
                                В общем «тема сисек не раскрыта»…
                                  0

                                  Где-то читал, что если объект создаётся и быстро удаляется, то это вполне хороший и дешёвый случай для GC

                                  0
                                  (структуры с помощью == сравнивать нельзя)

                                  Неужели?
                                  public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<int>, IEquatable<int>
                                    0
                                    User-defined struct types don't support the == operator by default. To support the == operator, a user-defined struct must overload it.

                                    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.

                                    давно перестал удивляться на собеседованиях… грустно…
                                      +3
                                      И?
                                      «don't support the == operator by default» не тождественно «структуры с помощью == сравнивать нельзя», да и «User-defined struct» это не все struct.
                                        0
                                        Именно об этом. Возможно просто забыли упомянуть и этот нюанс с «переопределением».
                                        Не более.
                                          +3
                                          Да как-то подозрительно много всего забыли. А ведь кто-то может начать применять на практике.
                                            0
                                            Мне, например, достаточно того, что я могу имплементировать свои операторы сравнения, IEquatable и т.д. Да, это требуется не часто, а где «не надо» — меня компилятор «мордой натыкает». Мне этого достаточно, что бы не задумываться о иных типах.
                                            Знание документации «наизусть» с учетом того, что она обновляется быстрее, чем успевают ее прочитать и применить — не наш метод.
                                            Зная некоторые принципы, можно не беспокоиться о многих фактах :)
                                    +7
                                    value type (пример int, struct, ссылки на инстансы reference type) хранятся в быстром stack

                                    Это неверно. Простейший контрпример:


                                    class RefType
                                    {
                                        int Value;
                                    }

                                    Value будет храниться вместе со всем объектом, в куче.
                                    Затем, замыкания, async-функции, генераторы, все они могут оправить value typed-значение в кучу.


                                    Затем, само наличие стека — это подробность реализации текущей Microsoft CLR, она не гарантирована по стандарту.


                                    Немного по теме: ответ на SO и статья Эрика Липперта, обсуждаемая в этом ответе.


                                    Кстати, и reference types не прибиты гвоздями к куче. Вот тут их от кучи отвязывают.

                                      +1
                                      Спасибо за комментарий и дополнения.

                                      Затем, само наличие стека — это подробность реализации текущей Microsoft CLR, она не гарантирована по стандарту.

                                      Интересно, спасибо.

                                      Это неверно. Простейший контрпример:

                                      Тут противоречия не вижу, в контрпримере class — в заметке отметил что class в куче (имеется ввиду все его value тоже).
                                        0

                                        Но в вашем тексте написано безусловно: «value type (...) хранятся в быстром stack», а не «локальные переменные значимых типов хранятся на стеке, за исключением (тут перечисление)».


                                        То, что поля хранятся там же, где и весь объект, вовсе не очевидно: например, если объект значимого, а поле ссылочного типа, то они хранятся не вместе.

                                      0
                                      В SOLID про I забыли — Interface segregation.
                                        0
                                        Спасибо, исправил.
                                        0
                                        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, они могут применяться как к ссылочным так и к значимым типам (тип и способ передачи это ортогональные понятия)
                                          0
                                          Спасибо за критику.

                                          для «передачи по ссылке» есть ключевые слова ref и out, они могут применяться как к ссылочным так и к значимым типам

                                          В разделе «ref и out» действительно не хватает пары пунктов (хотя и отмечено что ref, out применяются к ссылочным и значимым типам), допишу.

                                          По остальным пунктам, по поводу того что можно сделать (мелкую кучу, разместить ссылочный тип в стеке) — это все таки не темы для обсуждения на собеседовании для джуна/миддла. Выше в комментариях указали что «наличие стека — это подробность реализации текущей Microsoft CLR, она не гарантирована по стандарту». Да, это интересные темы, полезно об этом знать — спасибо что указали.
                                            +1
                                            Дело в том, что это не «для джуна/миддла» а повторение в корне неправильной точки зрения. Правильная она в ECMA 334, а если вы делаете шпаргалку для джунов не стоит им предлагать заведомо неверную информацию.
                                          0
                                          > Напишите singleton (не забудьте про потокобезопасность и lock)

                                          1. не надо путать singleton и lazy initialization (обычно это всё кидают в одну кучу)
                                          2. в c# уже давно есть и для одного и для другого готовые конструкции, вручную городить лок, дабл-чек и барьер не надо
                                            0
                                            вручную городить лок, дабл-чек и барьер не надо

                                            Хорошее дополнение, на собеседовании полезно сказать о встроенных возможностях. Но и реализацию singleton, т.к. это часто спрашивают на собеседованиях, нужно понимать.
                                            То же и с «сортировкой пузырьком» — никто её сам не пишет, но на собеседованиях спрашивают.
                                              0
                                              > Но и реализацию singleton, т.к. это часто спрашивают на собеседованиях, нужно понимать

                                              нужно ещё понмать, какая из них корректная, большинство из них, включая ту, которая написана в википедии, будут работать некорректно в зависимости от нагрузки.

                                              P.S.: пардон, проверил википедию, сейчас в статье Singleton реализация верная:
                                              public static Singleton Instance { get; } = new Singleton();


                                              раньше там был код из статьи про Double Check Locking без барьера, который работал или медленно или неправильно.
                                              0
                                              Подскажите, что-за готовые конструкции?
                                            0
                                            > const — значение подставляется при компиляции => установить можно только до компиляции
                                            > readonly — установить значение можно только до компиляции или в конструкторе

                                            я даже не буду ничего говорить.
                                            эта статья — какой-то один большой фейспалм…
                                              0
                                              > Finalizer (destructor) ~

                                              За «деструктор» в дотнете принято бить по морде. Снисхождение можно проявить только к заядлым плюсовикам. (но им надо объяснить что то, что они пишут как ~Foo() тут называется Dispose())

                                              > вызывается когда garbage collector доберется до объекта

                                              а вот и нет, гуглите SuppressFinalize
                                                0
                                                За «деструктор» в дотнете принято бить по морде

                                                Да, в заметке об этом явно написано:
                                                зачем может пригодиться переопределять finalizer: предпочтительней реализовать IDisposable
                                                  –1
                                                  в заметке есть слово «destructor» которое в принципе неприменимо ни к финализатору ни к Dispose().

                                                  более того, указано что GC вызовет финализатор когда доберётся до объекта, но по факту он может добраться до объекта и __не__ вызвать финализатор.

                                                  P.S.:
                                                  > зачем может пригодиться переопределять finalizer: предпочтительней реализовать IDisposable

                                                  так каноничный disposable pattern (если не считать новых disposable struct) как раз включает в себя правильный финализатор. По морде бьют не за ~Foo() а за называние «финализатора» «деструктором»
                                                    0
                                                    По морде бьют не за ~Foo() а за называние «финализатора» «деструктором»

                                                    Тогда Вам в microsoft-е надо порядок навести
                                                    docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/classes-and-structs/destructors
                                                    Finalizers (which are also called destructors) are used to


                                                    «Бить морды» за расхождения в терминологии — не наш метод.
                                                      –1
                                                      Эта статья имеет корни из 1999-го года, тогда то-ли не придавали внимания этим отличиям, то-ли Хайлсберг хотел сделать их детерминистичными, но позже не стал. В рантайме такой оговорки нет, там только финалайзеры.

                                                      Кроме как в этом дополнении слово «деструктор» там сейчас встречается ровно 0 раз.
                                                        0
                                                        Вы можете вести в гугле «destructor C#» и наслаждаться кучей ссылок. В том числе и на вполне себе приличные ресурсы.

                                                        Это я к тому что куча людей вполне себе используют слово «destructor» в контексте C# и в общем-то все друг-друга понимают.
                                                          –1
                                                          Вы можете вести в гугле «гомеопатия» и наслаждаться кучей ссылок
                                                            0
                                                            И что это должно доказывать или опровергать? Что люди используют это слово и никто никого за это по морде не бьёт?
                                                              0
                                                              В обычной жизни — наздоровье. Но при написании «обучающей» статьи нужно использовать самую точную терминологию, дабы никого не путать.
                                                                0
                                                                На мой взгляд остаточно использовать понятную. И уж если человек не понимает что в C# у нас GC и что надо понимать в этом контексте под словом «destructor», то ему надо начинать с азов.

                                                                И как ни крути, но уж если MS позволяет себе использовать это слово, то…
                                                                  +1

                                                                  Человека который имеет представление о C++, слово "деструктор" будет очень сильно сбивать. Не надо его использовать применительно к C#.

                                                                    0
                                                                    Убрал «деструктор».
                                                                      0
                                                                      ещё первый пункт уберите, потому что он неверный (контрпример есть в MSDN в статье про Disposable)
                                                                        0
                                                                        P.S.: и третий пункт

                                                                        > вызывается только автоматически средой .Net, нельзя вызвать самостоятельно

                                                                        myObj.GetType().GetMethod("Finalize",
                                                                        BindingFlags.NonPublic |
                                                                        BindingFlags.Instance |
                                                                        BindingFlags.DeclaredOnly)
                                                                        .Invoke(myObj, null);
                                                                        
                                                                          0
                                                                          Отметил про GC.SuppressFinalize и вызов Finalize с помощью reflection.
                                              0

                                              Наряду с ref, out сегодня есть ещё и in.

                                                0
                                                Спасибо, добавлю.
                                                0
                                                Да уж, получился знатный список вопросов-маркеров, по которым можно отличать тех, кто только читал, от тех, кто использовал, и уже тем более от тех, кто понял.
                                                Уровень описания в статье — junior.

                                                Даже middle должен бы знать, что string ни разу не похож на value type. У этого типа есть две ключевые особенности: immutable (любое изменение порождает новый экземпляр) и оптимизация компилятором (если в коде встречается несколько раз один строковый литерал, после компиляции это будет один и тот же объект)

                                                Так же middle должен понимать, для чего используются публичные методы object, и когда и для чего требуется их переопределять. И что GetHashCode надо переопределять совместно с Equals во избежание разногласий (но не для ускорения).

                                                Список можно продолжить. Но я обычно задаю вопросы вразрез, кандидат сам расскажет, что понимает и шпаргалки не помогут. Например: когда (в каких случаях) ValueType хранится в куче? Занимает ли ReferenceType место на стеке? Всегда ли вызывается Dispose? С какими паттернами разработчик сталкивался на предыдущем проекте (к примеру, типичный asp.net-разработчик сталкивается как минимум с пятью паттернами, но чаще всего их в упор не видит) и т.д.
                                                  0
                                                  Спасибо за комментарий.

                                                  string ни разу не похож на value type

                                                  Действительно, про immutable и StringBuilder надо добавить (в комментариях несколько раз на это указывали).
                                                  Видимо фраза из заметки
                                                  при присвоении (передаче в метод) и сравнении ведут себя как value type

                                                  не совсем точная, имелось ввиду «не смотря на то, что значение string хранится в куче, поведение string при сравнении (передаче в метод) совсем не как у reference type и больше похоже на value type: разные переменные строк с одинаковым значением при сравнении == дадут true, несмотря на то что ссылки у них разные ....».
                                                    +2
                                                    Во-первых, StringBuilder не при чём в дискуссии про value/reference type. Это отдельный класс, служащий своим целям.

                                                    Во-вторых, фраза
                                                    поведение string при сравнении (передаче в метод) совсем не как у reference type и больше похоже на value type:
                                                    не корректна. Не надо путать новичков, они потом это приносят на собеседовании и очень портят впечатление.

                                                    Я не единожды писал классы, которые
                                                    разные переменные с одинаковым значением при сравнении == дадут true, несмотря на то что ссылки у них разные
                                                    , и даже делал immutable классы. Ничто из это не имеет отношения ни к value type, ни к строкам.
                                                  +1

                                                  В разделе "Порядок инициализации" есть неточность в описании порядка инициализации статических полей и вызова статических конструкторов. Среда гарантирует, что статические поля будут инициализированы, и статический конструктор будет вызван до первого использования класса, отношение наследования никак на порядок статической инициализации не влияет. Например, если в статическом конструкторе наследника как-то используется базовый класс (статические поля или создаётся экземпляр), то инициализация статических полей и вызов статического конструктора базового класса произойдёт до аналогичных действий для наследника.

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое