Несколько вопросов по .NET и C# (ответы с разбором)

    На минувших выходных я запустил пост «Несколько вопросов по .NET и C#»

    Я думаю многим интересно не только найти правильные ответы, но и узнать какая часть пользователей знает эти правильные ответы. Именно с этой целью и создавался этот пост.

    Я не настаиваю на истинности объяснений в последней инстанции, поэтому конструктивная критика, дополнения и уточнения приветствуются.



    Обещанные ответы с разбором под катом.




    Этот вопрос наделал больше всего шума в комментариях.

    В спецификации C# сказано:
    The static constructor for a class executes at most once in a given application domain. The execution of a static constructor is triggered by the first of the following events to occur within an application domain:

    An instance of the class is created.
    Any of the static members of the class are referenced.

    Потому самый близкий к истине ответ: "Один раз при первом создании экземпляра класса или при первом обращении к статическим членам класса". Хотя в данном случае наверное переформулировка "… при первом упоминании" будет более уместна.

    Дело в том, что бывают статические конструкторы явные (когда конструктор задан явно) и неявные (присваивание значений статическим свойствам). В том случае, когда нет явно заданного статического конструктора, класс помечается флагом beforefieldinit, что говорит CLR о том, что инициализация статического поля произойдет до первого обращения к этому полю, причем она может произойти задолго до этого обращения.

    Кстати, на этом свойстве статического конструктора построена одна из реализаций Singleton'а
    public sealed class Singleton
    {
        static readonly Singleton instance = new Singleton();
    
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Singleton()
        {
        }
    
        Singleton()
        {
        }
    
        public static Singleton Instance
        {
            get
            {
                return instance;
            }
        }
    }
    





    Для подобных целей в C# предусмотрены ключевые слова add и remove. Их необходимо использовать аналогично get и set для свойств, то есть:

    public class MyClass {
      private EventHandler myEvent;
      public event EventHandler MyEvent
      {
        add { myEvent += value; }
        remove { myEvent -= value; }
      }
    }
    



    int i = 5;
    object o = i;
    long j = (long)o;
    

    Большая часть пользователей не знает правильного ответа на этот вопрос.

    Будет сгенерировано исключение InvalidCastException (Исключение, которое выбрасывается при недопустимом приведении или явном преобразовании типов). Ошибка находится в третьей строчке.

    Приведение типов — это преобразование значения переменной одного типа в значение другого типа, бывает явным (explicit) и неявным (implicit):
    int i = 123;
    long l = (long)i;  // explicit 
    
    int i = 123;
    long l = i;  // implicit 
    

    Существуют два вида типов: value-type и reference-type (типы-значения и типы-ссылки, соответственно), в тот момент, когда мы присваиваем переменной с ссылочным типом некоторое значение происходит boxing (упаковка) этого значения.
    Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type

    Обратный процесс называется unboxing (распаковка).

    Для того чтобы исправить ошибку достаточно изменить третью строчку long j = (int)o; Сначала будет произведен unboxing и возвращена переменная типа int, после чего будет вызван соответствующий implicit оператор.



    Все, кроме директивы #typedef и #elseif.

    +UPD
    С помощью #define нельзя писать макросы, как, например, в С++, но можно устанавливать значения. Директивами #if, #else, #endif проверяется, установлено ли значение. Атрибут Conditional служит для указания компилятору компилировать или не компилировать метод, в зависимости от того установлено ли соответствующее значение #define, что нужно исключить из сборки все вызовы данного метода. Сам метод останется в сборке и его можно будет вызвать с помощью Reflection.

    Пример с использованием Conditional
    #define TRACE_ON
    using System;
    using System.Diagnostics;
    
    public class Trace
    {
        [Conditional("TRACE_ON")]
        public static void Message(string msg)
        {
            Console.WriteLine(msg);
        }
    }
    
    [Conditional("DEBUG")]
    public void Test()
    {
        MessageBox.Show("I want cookie");
    }
    ctor()
    {
        this.Test();
        this.GetType().InvokeMember("Test", BindingFlags.InvokeMethod, null, this, null);
    }
    


    -UPD


    В C# создавать вложенные пространства имен можно либо написав их имена через точку, либо вложив одно в другое с помощью составного оператора { }.



    Чтобы использовать unsafe код необходимо вызывать компилятор с ключом /unsafe, этот ключ также можно установить в настройках проекта. Помимо этого каждый метод с unsafe кодом необходимо пометить ключевым словом unsafe.



    Рассмотрим пример с кнопкой. Создав объект класса Button, мы подписываемся на его событие Click, которые происходит при нажатии на кнопку. В данном случае, объект Button является издателем (publisher), а тот метод, который подписан на событие, соответственно, подписчиком (subscriber).



    Во время компиляции значения констант подставляются в места их использования.


    Большая часть пользователей не знает правильного ответа на этот вопрос.

    Все перечисленные элементы можно найти в типе-перечислении AttributeTargets, значения которого используется в атрибуте AttributeUsage, служащего для указания, что может быть помечено атрибутом.

    Пример использования атрибута, примененного к возвращаемому значению:
    using System.Runtime.InteropServices;
    
    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern Boolean MessageBeep(UInt32 uType);
    




    Существует специальное пространство System.Runtime.InteropServices, в котором находятся классы для работы с неуправляемым кодом, например, хорошо известный атрибут DllImport, для подключения DLL-функций.



    Атрибутом Obsolete отмечается нерекомендуемая для использования сущность программы. Каждый случай использования сущности, отмеченной устаревшей, будет приводить к генерированию предупреждения или ошибки в зависимости от настроек этого атрибута.

    +ADD
    Про настройки проекта в вопросе не упомянуто, значит, берём поведение по умолчанию. Указание атрибута: [Obsolete]
    На методе в соданном решении, без изменения свойств решения приведёт именно к предупрежению:
    public ObsoleteAttribute()
    {
        this._message = null;
        this._error = false;
    }
    

    -ADD

    Подробно на MSDN



    Структуры не поддерживают наследование, но могут реализовывать интерфейсы. Также одной из особенностей является невозможность определения конструктора по умолчанию, так как поля инициализируются значениями по умолчанию автоматически. Но, однако, возможно определение статического конструктора.
    Share post

    Similar posts

    Comments 37

      +13
      Круто облажались многие хабравчане :) Лишний раз напоминает о том, что гонору много, а знаний нет.

      Хотя, справедливости ради, такие тонкости помнить необязательно. Лучше иметь хорошую голову, а нюансы всегда можно у рихтера подсмотреть.
        +8
        Мне кажется, вполне адекватные вопросы, без каких-то глубочайших знаний язык. Поставил в тупик только вопрос с атрибутом Obsolete, который, как мне кажется, достаточно специфичным для повседневного использования. Ну и первый вариант ответа на первый вопрос — достаточно спорный.

        В целом же мне кажется, что это не самый объективный опрос знаний C# на Хабре. Уверен, многие C и C++ разработчики тоже потыкали на кнопочки, многие решили наугад пощелкать, кто-то только начала изучать язык и т.д. Интересно было бы узнать, сколько реальных разработчиков участвовало в опросе, и как они отвечали.
          +1
          Надо было в конце добавить вопрос «Вы действующий C# разработчик?» )
          +2
          Часть вопросов сродни «Чем отличаются ref и out» из статьи про программиста в Нью-Йорке неподалеку.
          Не видел ни одного проекта на C#, где была бы условная компиляция. Насчет obsolete — если подобный код используется в проекте, то логично, что ничего больше варнинга выдаваться не будет. А от них избавляются обычно в последнюю очередь. Unsafe код — специфичная вещь, и большинство C# разработчиков с ним не столкнутся (я сейчас говорю о нормальных проектах). Если надо — ответы на подобные вопросы легко найти.
          В остальном вопросы довольно интересны.
          Но, умение к месту применять паттерны и писать KISS код гораздо важнее вызубренного msdn имхо.
            +2
            По поводу условной компиляции — winrt/winphone во все поля.

            А вообще, да — найти ответы на эти вопросы действительно очень легко. Но тут дело в том, что попытка найти ответ на такие простые вопросы зачастую приводит к новым, уже более глубоким вопросам. Ну, банально — элементарный же вопрос про константы и ридонли поля. Вроде, разница понятна, а что из этого следует? Когда использовать одно, а когда другое? Постепенно углубляемся до обратной совместимости сборок — уже, вроде, и не так банально.

            Или вот с упаковкой — автор статьи привел весьма скромное объяснение, но там всплывают и более серьезные вопросы, вроде, а какие, вообще, бывают преобразования типов и чем отличаются?

            Короче говоря, не все очевидные вопросы имеют очевидную подоплеку.
              0
              Простите, вы случаем сборы военные в ЛО не проходили? «во все поля» — это довольная знаменитая фраза одного военного :).
                0
                Нет. Это, вроде, был довольно популярный мем когда-то.
              +1
              Многие MVVM-фреймворки (например, Caliburn Micro) используют условную компиляцию для того, чтобы обеспечить портируемость на Silverlight \ WinRT \ Compact Framework. У нас на работе тоже используются — у продукта есть несколько редакций (basic, pro, corporate, trial) в зависимости от которых тот или иной код подключается или отключается, а также разные сервера для release \ debug сборок, адреса которых указываются также в директивах препроцессора.
                0
                Но, умение к месту применять паттерны и писать KISS код гораздо важнее вызубренного msdn имхо.

                Дойче банк с вами явно не согласен.
                  0
                  Каким образом выражается несогласие? Собеседующие-неадекваты?
                    +1
                    Выражается в том, что собеседующих абсолютно не интересует опыт работы или умение писать простой, красивый, понятный код. KISS, DRY, SOLID? Нет, не слышали. Расскажите лучше физический смысл второй производной. Ну и что, адекватный вопрос для разработчика игрового 3d-движка, скажет кто-нибудь? Вот только это собеседование на вакансию разработчика UI. Думаю, уместнее были бы вопросы по теории дусамфинга. Для тех, кто не читал:
                    xxx: седня узнал новое понятие из словаря разработчиков дойчебанка — заниматься дусамфингом
                    xxx: пример: — Чем этот чилавег у вас там занимается уже полгода? Ответ: Ай ду нот ноу, ду самфинг
                    xxx: есть целые команды, которые занимаются дусамфингом
                    xxx: это распространено
                0
                Надо думать, что отвечали не только C#/.Net программисты. Я, например, тоже внес свою лепту :), хотя на большинство вопросов ответил правильно/почти правильно.
                +4
                Требую продолжения!
                  0
                  #elif же, а не #elseif
                    0
                    Ой… это я с VB уже напутал. Спасибо. Исправил.
                      0
                      Текст под картинкой тоже исправьте — теперь они расходятся.
                    0
                    *
                      +4
                      К вопросу №1.
                      Стандарт ECMA-335 (.pdf), пункт I.8.9.5.
                      I.8.9.5 Class type definition

                      An explicit class definition:

                      • Can optionally specify a method (called .cctor) to be called to initialize the type.

                      The semantics of when and what triggers execution of such type initialization methods, is as
                      follows:
                      1. A type can have a type-initializer method, or not.
                      2. A type can be specified as having a relaxed semantic for its type-initializer method
                        (for convenience below, we call this relaxed semantic BeforeFieldInit).
                      3. If marked BeforeFieldInit then the type’s initializer method is executed at, or
                        sometime before
                        , first access to any static field defined for that type.
                      4. If not marked BeforeFieldInit then that type’s initializer method is executed at (i.e.,
                        is triggered by):
                        1. first access to any static field of that type, or
                        2. first invocation of any static method of that type, or
                        3. first invocation of any instance or virtual method of that type if it is a value
                          type or
                        4. first invocation of any constructor for that type.
                      5. Execution of any type's initializer method will not trigger automatic execution of
                        any initializer methods defined by its base type, nor of any interfaces that the type
                        implements.

                      For reference types, a constructor has to be called to create a non-null instance. Thus, for
                      reference types, the .cctor will be called before instance fields can be accessed and methods can
                      be called on non-null instances.

                      If a language wishes to provide more rigid behavior—e.g., type initialization automatically
                      triggers execution of base class’s initializers, in a top-to-bottom order—then it can do so by
                      either:
                      ...
                      • by making explicit calls to
                        System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor

                      Три отмеченных пункта делают вариант ответа №1 неверным.
                        +4
                        Есть здесь JetBrains?

                        namespace System::Customizer
                        {
                            public class Foo { }
                        }


                        R# никаких синтаксических ошибок не показывает, однако не компилируется
                          +1
                          Спасибо за наводку, создал кейс в трекере. Судя по всему, на названии пространств имен валидации нет вообще. Будем разбираться.
                          0
                          По поводу const vs readonly тоже можно побольше написать. Например, что будет, если скомпилировать сборку, в которой используется константа из другой сборки.
                            0
                            Везде можно побольше написать, я старался написать побольше там, где меньше правильных ответов.
                              +2
                              Недавно натолкнулся на эту разницу при написании компилятора. Оказывается, даже если поле получается через Type.GetField(), с ним можно взаимодействовать командой ldfld только если у этого поля флаг IsLiteral == false. В противном случае необходимо разрешать значение на уровне компилятора и прошивать его значение константой — иначе вылезает MissingFieldException.

                              Если кому-нибудь интересно, могу написать небольшую на тему того, какие тонкости и подводные камни ждут желающих писать .NET-совместимые языки.
                                +4
                                Да! Интересно — пишите!
                                0
                                Например, что будет, если скомпилировать сборку, в которой используется константа из другой сборки.
                                Отвечу за автора:
                                Константа попадает в кучу "#US" в метаданные сборки, которая ссылается на родительскую сборку.
                                При этом, данное поведение характерно не только для .NET, но и для C++. (Естественно, без метаданных).

                                Т.е. при изменении константы в родительском PE файле и без перекомпиляции дочернего PE файла константа останется прежней.
                                  0
                                  Константа попадает в кучу "#US" в метаданные сборки

                                  Ой, фигню написал. Правильно будет:
                                  Константа попадает в поток #US (Unicode Stream) находящейся в куче метаданных.
                              • UFO just landed and posted this here
                                  +1
                                  Методы, помеченные как Conditional удобны тем, что не нужно писать #if — #endif раскоряку. В стандартных классах используется, например, в методах System.Diagnostics.Debug — WriteLine, Assert и т.п. Напоминает partial методы, которые можны вызывать, даже если они не имеют тела.
                                    +1
                                    используем в фирме более привычные С-шному программисту #if
                                    Правильно делаете. Т.к. один из первых хаков Ultrapico Espresso (не знаю как сейчас) заключался в том, что в коде был оставлен тестовый метод с атрибутом Conditional, который запускал программу без проверки ключа лицензии.
                                    Т.е. для кряка достаточно было загрузить сборку в память и через рефлексию вызвать тестовый метод.
                                    0
                                    А откуда вопросы?

                                    В It Academy кажется один в один
                                      0
                                      Да, эти вопросы выборочно взяты из открытого тестирования ВМК МГУ по программе Microsoft It Academy для сертификации респондентов.
                                      +2
                                      Ваш вариант ответа на вопрос 11 очень спорный. Мне все таки кажется верными будут 2-й и 4-й варианты ответа. Изначально генерируется предупреждение. В студии можно поставить warning as error, но в этом случае про любое предупреждение можно говорить — «оно будет либо предупреждение либо ошибка в зависимости от настроек студии». Тут неточность в формулировке и скорее из за этого большинство ответили на вопрос именно так (гадая что вы имели ввиду).
                                        0
                                        Как по мне, там три варианта ответа подходит — 1, 2 и 4, в зависимости от ситуации.
                                        Например, если я помечу свойство атрибутом и где нибудь буду использовать это свойство — я получу предупреждение.
                                        А если я, как второй параметр этого атрибута, поставлю true и буду использовать это свойство — то получу ошибку компиляции.
                                        А если я не буду использовать свойство с этим атрибутом вообще — то всё будет хорошо и это никак не повлияет на компиляцию.
                                        0
                                        Интересно зачем это нужно. Я вот сделал много проектов с нуля на C# / .Net, и ответы на многие вопросы так никогда и не узнаю в своей жизни. И считаю, что это хорошо. Потому что голова свободна для полезной информации. А если мне что-то нужно, то всегда могу посмотреть в справке.
                                          +1
                                          Немного критики:
                                          1) Атрибут Conditional служит для указания компилятору компилировать или не компилировать метод, в зависимости от того установлено ли соответствующее значение #define.
                                          Если Вы посмотрите IL код, то найдёте свой метод скомпилированным в коде, но вот все вызовы метода будут удалены.
                                          Т.е. при написании такого кода:
                                          [System.Diagnostics.Conditional("TEST")]
                                          public void Test()
                                          {
                                          	MessageBox.Show("I want cookie");
                                          }
                                          ctor()
                                          {
                                          	this.Test();
                                          	this.GetType().InvokeMember("Test", BindingFlags.InvokeMethod, null, this, null);
                                          }
                                          

                                          Метод Test() выполнится хотя-бы единожды.

                                          2) Атрибутом Obsolete отмечается нерекомендуемая для использования сущность программы. Каждый случай использования сущности, отмеченной устаревшей, будет приводить к генерированию предупреждения или ошибки в зависимости от настроек этого атрибута.

                                          Про настройки проекта в вопросе не упомянуто, значит, берём поведение по умолчанию. Указание атрибута:
                                          [Obsolete]
                                          На методе в соданном решении, без изменения свойств решения приведёт именно к предупрежению:
                                          public ObsoleteAttribute()
                                          {
                                          	this._message = null;
                                          	this._error = false;
                                          }
                                          

                                            0
                                            Серьезно… Внес исправления в пост.
                                            0
                                            поломались картинки, а в них вопросы — самое важное в этой статье.
                                            исправьте пожалуйста.

                                            Only users with full accounts can post comments. Log in, please.