Pull to refresh

Каверзные вопросы по C#

Reading time 7 min
Views 91K

Хочу представить вашему вниманию комические купле каверзные вопросы по C#.
Не удержался и решил запостить немного классики.
Некоторые вопросы в подборке кому-то могут показаться слишком простыми, но небольшой подвох в них, как правило, есть. Иногда можно и простым вопросом подловить. Будут полезны тем, кто изучает язык.
Всех, кому интересно, прошу под кат!

1 Какой будет результат выполнения следующего кода?

   static String str;
   static DateTime time;

        static void Main(string[] args)
        {
            Console.WriteLine(str == null ? "str == null" : str);
            Console.WriteLine(time == null ? "time == null" : time.ToString());
            Console.ReadLine();
        }

Ответ
str == null
1/1/0001 12:00:00 AM

Обе переменные не инициализированы, но string это ссылочный/reference тип (если быть более точным, то это immutable тип, что означает reference тип с семантикой value типа), а DateTime это тип значения/value type. Значение по умолчанию неинициализированного типа DateTime это 12:00 1 января 1 года.


2 Поиграем с наследованием. Что будет выведено на экран?

        class A
        {
            public void abc(int q)
            {
                Console.WriteLine("abc из A");
            }
        }

        class B : A
        {
            public void abc(double p)
            {
                Console.WriteLine("abc из B");
            }
        }

        static void Main(string[] args)
        {
            int i = 5;
            B b = new B();
            b.abc(i);
            Console.ReadLine();
        }

Ответ
abc из B


3 Похожий вопрос. Что будет результатом?

        class P
        { }
        class Q : P
        { }

        class A
        {
            public void abc(Q q)
            {
                Console.WriteLine("abc из A");
            }
        }

        class B : A
        {
            public void abc(P p)
            {
                Console.WriteLine("abc из B");
            }
        }

        static void Main(string[] args)
        {
            B b = new B();
            b.abc(new Q());
            Console.ReadLine();
        } 

Ответ
abc из B
Здесь все чуть более очевидно по сравнению с прошлым примером.


4 Типичный «развод» на понимание полиморфизма. Главное ничего не забудьте и не упустите из виду.
Какой будет результат выполнения следующего кода?

    class Program
    {
        static void Main(string[] args)
        {
            MyClassB b = new MyClassB();
            MyClassA a = b;
            a.abc();
            Console.ReadLine();
        }
    }

    class MyClassA
    {
        public MyClassA()
        {
            Console.WriteLine("constructor A");
        }

        public void abc()
        {
            Console.WriteLine("A");
        }
    }

    class MyClassB:MyClassA
    {
        public MyClassB()
        {
            Console.WriteLine("constructor B");
        }

        public void abc()
        {
            Console.WriteLine("B");
        }
    }

Ответ
constructor A
constructor B
A

При инициализации класса B будет выполнен конструктор по умолчанию класса А, потом конструктор класса B. После присвоения переменной типа класса А значения b мы получим в ней экземпляр класса B. Казалось бы должна быть вызвана abc() из класса B, но так как в классе B не указан никакой предикат у метода abc, то выходит что он скрывает abc из класса A. Пример не совсем правильный и abc() в классе B будет подчеркнуто, так как требуется предикат new.


5 У меня есть такой вот класс:

        public class Point
        {
            public int X { get; set; }
            public int Y { get; set; }
            public Point(int xPos, int yPos)
            {
                X = xPos;
                Y = yPos;
            }
        }

И есть 3 экземпляра класса. Сработает ли подобная инициализация третьего экземпляра? Если нет, то что нужно сделать?

            Point ptOne = new Point(15, 20);
            Point ptTwo = new Point(40, 50);
            Point ptThree = ptOne + ptTwo;

Ответ
Пример работать, конечно же, не будет. Для того, чтобы этот код заработал, в класс Point нужно добавить перегрузку оператора сложения. Например, вот такой:

            public static Point operator +(Point p1, Point p2)
            {
                return new Point(p1.X + p2.X, p1.Y + p2.Y);
            }



6 Какой будет результат выполнения следующего кода?

        string result;

        private async void btnStart_Click(object sender, RoutedEventArgs e)
        {
            SaySomething();
            txtSomeTextBlock.Text = result;
        }

        async System.Threading.Tasks.Task<string> SaySomething()
        {
            await System.Threading.Tasks.Task.Delay(1000);

            result = "Hello world!";
            return result;
        }

Ответ
Будет выведена пустая строка, а не «Hello world!». Таск SaySomething() был вызван без await и потому SaySomething выполняется синхронно до первого await, то есть до строки

await System.Threading.Tasks.Task.Delay(1000);

После чего выполнение возвращается к btnStartClick. Если использовать await при вызове SaySomething(), то результат будет ожидаемым и на экран будет выведен текст «Hello world!»


7 Вопрос из разряда «must know». Какой будет результат выполнения следующего кода?

    delegate void SomeMethod();

        static void Main(string[] args)
        {
            List<SomeMethod> delList = new List<SomeMethod>();
            for (int i = 0; i < 10; i++)
            {
                delList.Add(delegate { Console.WriteLine(i); });
            }

            foreach (var del in delList)
            {
                del();
            }
        }

Ответ
Программа отобразит число 10 десять раз.
Делегат был добавлен 10 раз. Причем была взята ссылка на переменную i. Ссылка, а не значение. Вот поэтому при вызове делегата берется последнее значение переменной i. Это типичный пример замыкания (closure)


8 Что будет выведено на экран следующим кодом?

        static bool SomeMethod1()
        {
            Console.WriteLine("Метод 1");
            return false;
        }

       static bool SomeMethod2()
        {
            Console.WriteLine("Метод 2");
            return true;
        }
        static void Main(string[] args)
        {
            if (SomeMethod1() & SomeMethod2())
            {
                Console.WriteLine("блок if выполнен");
            }
        }

Ответ
Метод 1
Метод 2
Блок if выполнен не будет, так как SomeMethod1 возвращает false. Но, так как используется логический оператор &, то будет проверено и второе условие – SomeMethod2. Если бы использовался более привычный оператор &&, то проверено было бы только значение первого метода.


9 Еще один простой (даже можно сказать бородатый) вопрос. Что будет результатом выполнения следующего кода?

            double e = 2.718281828459045;
            object o = e; // box
            int ee = (int)o;

Ответ
Этот код работать не будет и вызовет исключение в последней строке. Хотя казалось бы следующий кастинг (casting или иначе выражаясь explicit conversion) ошибки не вызывает, а только теряет дробную часть числа.

            double e = 2.718281828459045;
            int ee = (int)e;

Но при unboxing-е происходит проверка, содержит ли объект значение запрашиваемого типа. И только после этой проверки значение копируется в переменную.
А вот следующий, сделает unboxing без ошибки

            int ee = (int)(double)o; 

Следующий код также сперва выполнить приведение объекта o к типу dynamic и затем без проблем произведет уже casting, а не unboxing:

            int ee = (int)(o as dynamic);

Впрочем, он эквивалентен следующему коду:

     int ee = (int)(o is dynamic ? (dynamic)o : (dynamic)null);

и в результате фактически будет идентичен первому примеру:

            int ee = (int)(dynamic)o;

хотя, казалось бы, является новым трюком.


10 Что произойдет в результате выполнения данного кода?

            float q = float.MaxValue;
            float w = float.MaxValue;

            checked
            {
                float a = q * w;
                Console.WriteLine(a.ToString());
            }

Ответ
На экран будет выведено:
Infinity
float и double не являются integral types и потому в случае использования checked переполнения не возникает. Хотя если бы мы использовали int, byte, short или long то ожидаемо возникла бы ошибка. Unchecked также не будет работать с не встроенными типами. Например:

            decimal x = decimal.MaxValue;
            decimal y = decimal.MaxValue;

            unchecked
            {
                decimal z = x * y;
                Console.WriteLine(z.ToString());
            }

Сгенерирует исключение System.OverflowException


11 Можно еще поиграть с типом decimal. Если что-то будет выведено на экран, то что?

            int x = 5;
            decimal y = x / 12;
            Console.WriteLine(y.ToString());

Ответ
Этот пример выведет 0 из-за того что x поленились привести к типу decimal. Так как x у нас целочисленного типа, то при делении 5 на 12 получается число меньше чем 1, значит это целочисленный ноль. Верный результат выведет строка:

   decimal y = (decimal)x / 12;



12 Что произойдет в результате выполнения следующего кода:

            double d=5.15;
            d = d / 0;

            float f = 5.15f;
            f = f / 0;

            decimal dc = 5.12m;
            dc = dc / 0;

Ответ
Деление на 0 типов double и float вернет Infinity, а вот decimal вызовет исключение System.DivideByZeroException

На всякий случай коротенько о различиях между decimal, double и float:
decimal (128-битный тип данных, точностью 28–29 знаков после запятой) — используется в финансовых калькуляциях которые требуют высокой точности и отсутствия ошибок при округлении
double (64-битный тип данных, точностью 15–16 знаков после запятой) — обычный тип для хранения значений с плавающей запятой. Используется в большинстве случаев (кроме финансовых)
float (32-битный тип данных, точностью 7 знаков после запятой) — тип с самой низкой точностью и самым низким диапазоном, зато с самой высокой производительностью. Возможны ошибки при округлениях. Используется для высоконагруженных расчетов.


13 Допустим, имеется такой вот метод:

int SomeMethod(int x, int y)
    {
        return (x - y) * (x + y);
    }

Можно ли вызвать его вот так:

SomeMethod(y:17, x:21)

Ответ
Да, можно. Еще можно вызвать и вот так:

SomeMethod(11, y:27)

Но нельзя так:

SomeMethod(x:12, 11)

UPDATE: Начиная с C# 7.2 совершение такого вызова скорее всего станет возможным


14 Что произойдет в результате выполнения данного кода?

        static void Main(string[] args)
        {
            int someInt;
            SomeMethod2(out someInt);
            Console.WriteLine(someInt);

            SomeMethod1(ref someInt);
            Console.WriteLine(someInt);

            SomeMethod(someInt);
            Console.WriteLine(someInt);

            Console.ReadLine();
        }


        static void SomeMethod(int value)
        {
            value = 0;
        }
        static void SomeMethod1(ref int value)
        {
            value = 1;
        }
        static void SomeMethod2(out int value)
        {
            value = 2;
        }

Ответ
Ничего страшного не произойдет. Будет выведено на экран:
2
1
1
Так как первым мы вызываем SomeMethod2 с ключевым словом out, то значит someInt может быть передана без инициализации. Если бы мы использовали SomeMethod или SomeMethod1, то возникла бы ошибка компиляции.
Так как SomeMethod в параметре не содержит ключевого слова ref или out, то значение в этот метод передается по значению, а не по ссылке, а значит someInt не изменяется.
Ключевые слова ref и out означают, что значения передаются по ссылке. Но во втором случае в методе параметру обязано быть задано значение. В нашем примере, в методе SomeMethod2 параметру value обязательно должно быть присвоено значение.


15 Сработает ли код?

        static void Main(string[] args)
        {
            goto lalala;
            int i = 5;
            { Console.WriteLine(i); }
            lalala:
            Console.WriteLine("Прощай, жестокий мир! (=");
            Console.ReadLine();
        }

Ответ
Да, хоть и выглядит непривычно. На экран будет выведено только Прощай, жестокий мир!
Внутри метода вполне можно объявить между фигурными скобками включающую локальную область. Переменные из этой области будут недоступны вне ее. То есть вот такой вот код не скомпилируется:

        static void Main(string[] args)
        {
            { int i = 10; }
            Console.WriteLine(i);
        }

Как ни странно, но goto в C# все еще поддерживается. Хотя не особо нужен.


16 Что будет выведено на экран

        string hello = "hello";
        string helloWorld = "hello world";
        string helloWorld2 = "hello world";
        string helloWorld3 = hello + " world";

        Console.WriteLine(helloWorld == helloWorld2);
        Console.WriteLine(object.ReferenceEquals(helloWorld, helloWorld2));
        Console.WriteLine(object.ReferenceEquals(helloWorld, helloWorld3));
        Console.ReadLine();


Ответ
Будет выведено: True, True, False
Это типичный пример интернирования строк. Ситуации, когда строки, хранящие одно и то же значение представляют из себя один объект в памяти. Этот механизм позволяет чуть более экономно расходовать память.


17 Можно ли в C# использовать указатели как в C++?

Ответ
Можно внутри метода объявленного с модификатором unsafe или внутри блока unsafe

unsafe {  
    int a = 15; 
    *b = &a;
    Console.WriteLine(*b); 
          } 

Необходимо не забыть в свойствах проекта указать «Allow unsafe code»


18 Что такое дословные/точные строки (verbatim strings)?

Ответ
Все прекрасно знают что это за строки и постоянно используют их, но далеко не все знают название.
Verbatim strings — это строки, которые начинаются с символа @ и в которых не обрабатываются escape-последовательности.
Как их правильно называть на русском — дословные или точные это уже отдельный вопрос.


Бонус:
В чем отличие readonly и const?
Может ли класс иметь static конструктор?
Может ли быть try без catch?
Tags:
Hubs:
+20
Comments 86
Comments Comments 86

Articles