次の方法で共有


Ответы на задачи C# + Бонусная задача

Спасибо всем тем кто поучаствовал и предложил свои варианты ответов! Вот и я сам привожу исходный текст задачки и ответ с кратким пояснением. В конце поста вас ждет еще одна маленькая, но интересная задачка.

1. Каков результат компиляции и выполнения приведенного ниже кода?

static void Main(string[] args)
{
    Console.WriteLine(GetSomeResult(10000));
}

static long GetSomeResult(long someValue)
{
    long value1 = 10 * 1000 * 10000 * someValue;
    long value2 = 10 * 1000 * 10000 * 100000;
    return value2 / value1;
}

Ответ: из-за строчки long value2 = 10 * 1000 * 10000 * 100000 возникает ошибка компиляции (переполнение int), поскольку вычисления производятся в int арифметике до присвоения значения переменной value2. Чтобы ошибки не возникало, нужно использовать один из параметров типа long, для этого можно явно указать литеру L у одного из чисел   long value2 = 10 * 1000 * 10000 * 100000L;

2. Какое значение присвоено x, если приведенный ниже код выводит False?

float x;
Console.Write(x == x)

Ответ: по спецификации это особенность NaN. Т.е. float x = float.NaN;

3. А почему следующий код выводит False?

public static void Main()
{
    Test t = new Test();
    Console.WriteLine(t.Equals(t));
}

Ответ: этот вопрос - ловушка для людей ожидающих подвоха. Описанное поведение можно воспроизвести опледелив класс Test следующим образом: public class Test { public bool Equals(Test t) { return false; } }. Общее правило, которое подчеркивает эта задача - не стоит переопределять поведение методов с одинаковыми названиями - это обязательно запутает пользователей кода.

4. Что будет выведено на экран при выполнении приведенного ниже кода?

static void Main(string[] args)
{
    char a = 'a';
    int b = 0;
    Console.WriteLine(true ? a : b);
}

Ответ: 97. Типы операндов не совпадают и компилятор приводит переменные к общему совместимому типу Int32.

5. А в этом случае, что будет на экране?

NameValueCollection col = new NameValueCollection();
Console.WriteLine("Элемент test " + col["test"] != null ? "Существует!" : "Не существует!");

Ответ: Существует. Сначала работает оператор +, после этого сравнивается с null строка "Это test".

6. Что следует ожидать на экране?

Console.WriteLine("A" + "B" + "C");
Console.WriteLine('A' + 'B' + 'C');

Ответ: ABC 198. Конкатенация для символов не определена, поэтому будут получены числовые значения, затем просуммированы.

7. Циклическая инициализация полей? Интересненько, а в результате что будет на консоли выведено?

public class A { public static int x = B.y + 1; }
public class B { public static int y = A.x + 1; }

static void Main(string[] args)
{
    Console.WriteLine("A.x = " + A.x);
    Console.WriteLine("B.y = " + B.y);
}

Ответ: A.x = 2 B.y = 1. Тут все просто: вызывается конструктор A, затем конструктор B, но т.к. не определено значение A.x, то для используется 0 в конструкторе B.

8. Инкремент, инкремент, а что же будет?

int j = 0;

for (int i = 0; i < 10; i++)
    j = j++;

Console.WriteLine(j);

Ответ: 0. Подлый вопрос, поскольку тут внимание отвлекает ++. Но, на деле, оператор ++ возвращает значение переменной до инкрементации, поэтому j сохраняет исходное значение. (код, по сути, аналогичен z = j; j = j + 1; j = z ).

9. А что будет выведено в результате такого цикла?

int end = int.MaxValue;
int begin = end - 100;
int counter = 0;

for (int i = begin; i <= end; i++)
    counter++;

Console.WriteLine(counter);

Ответ: цикл бесконечный по определению - все значения int меньше или равны int.MaxValue.

10. А такого?

float begin = 1000000000;
int counter = 0;

for (float i = begin; i < (begin + 10); i++)
    counter++;

Console.WriteLine(counter);

Ответ: бесконечный цикл. Для таких больших значений float нет разницы между begin + 1 и begin + 10. Вообще, использовать не целые счетчики циклов - моветон и дорога в ад.

11. Какой же метод выберет компилятор?

class A { public void Test(int n) { Console.WriteLine("A"); } }
class B : A { public void Test(double n) { Console.WriteLine("B"); } }

static void Main(string[] args)
{
    B b = new B();
    b.Test(5);
}

Ответ: В. Приоритет отдается методу класса, по типу ссылки, если в нем определен метод с типами аргументов позволяющих выполнить преобразование без потерь.

12. А в этом случае?

public class Test
{
    public Test(object obj) { Console.WriteLine("object"); }
    public Test(int[] obj) { Console.WriteLine("int[]"); }
}

public static void Main() { Test t = new Test(null); }

Ответ: Int[]. Компилятор при определении вызова не использует текущее значение, а выбирает наиболее «специфический» конструктор. Нужно явно указать указать тип, если есть желание вызвать конструктор object: new Test((object)null);

13. Что будут выведено на экран в результате выполнения кода приведенного ниже?

List<int> list = new List<int>() { 1, 2, 3, 4, 5 };

List<int> all = list.FindAll(
i => { Console.Write(i); return i < 3; }
);

Ответ: 12345. Делегат, переданный методу FindAll вызывается для каждого элемента. Вопрос на внимательность, поскольку делегат используется как фильтрующая функция, применяемая к каждому элементу.

14. А такого кода?

List<int> list = new List<int>() { 1, 2, 3 };
var x = list.GroupBy(i => { Console.Write(i); return i; });
var y = list.ToLookup(i => { Console.Write(i); return i; });

Ответ: 123. Выполнение GroupBy отложено до обращения к результату. Вывод 123123 будет если дописать, например, строку var z = x.ToArray();

15. И наконец, сложный вопрос из трех частей. Что будет выведено на экран в каждом из трех случаев, приведенных ниже:

А)

try {
    Console.WriteLine("Hello ");
    return;
}
finally { Console.WriteLine("Goodbye "); }
Console.WriteLine("world!");

Ответ: Hello Goodbye. Finally выполняется даже если выполнение прервано по return.

Б)

try {
    Console.WriteLine("Hello ");
    Thread.CurrentThread.Abort();
}
finally { Console.WriteLine("Goodbye "); }
Console.WriteLine("world!");

Ответ: Hello Goodbye. Abort выбрасывает исключение ThreadAbortException, которое обрабатывается finally, затем выполнение прерывается.

В)

try {
    Console.WriteLine("Hello ");
    System.Environment.Exit(0);
}
finally { Console.WriteLine("Goodbye "); }
Console.WriteLine("world!");

Ответ: Hello. Выполнение программы прерывается в точке вызова System.Environment.Exit(0) и управление передается ОС. Слишком жестко наступаем песне на горло.

 

На последок, небольшой бонус - задача, которую мне прислал Владимир Биллиг. Как вы думаете, что будет выведено в консоли в результате выполнения следующего кода (и почему):

byte b1 = 1, b2 = 2, b3 = b1 + b2;

if (b3 > b1)
    Console.WriteLine("OK!");
else
    Console.WriteLine("wow!");

Comments

  • Anonymous
    November 10, 2008
    Гайдар, ты что-то перепутал в 11 и 12 номерах. Ответы не вяжутся с вопросами. В любом случае, оба примера компилируются без ошибок. В первом случае будет "В", во втором - int[].

  • Anonymous
    November 10, 2008
    Точно, перепутал. Спасибо за замечание! Не буду больше ночью в блог писать, мерещиться то, чего нет и ответы подписываются не под те вопросы :) Не компилировалось бы, например, в этом случае: public class Test    {        public Test(object obj) { Console.WriteLine("object"); }        public Test(int[] obj) { Console.WriteLine("int[]"); }        public Test(float[] obj) { Console.WriteLine("float[]"); }    }    public static void Main()  { Test t = new Test(null); }

  • Anonymous
    November 10, 2008
    Похоже, вы забыли добавить ответы на 5-е задание :)

  • Anonymous
    November 10, 2008
    Условие бонусной задачи указано не совсем верно. В данном случае код не удастся скомпилировать. Сумму необходимо привести к типу byte: byte b3 =(byte)(b1 + b2), иначе сумма будет считаться int. byte b1 = 1, b2 = 2; byte b3 =(byte)(b1 + b2); if (b3 > b1) Console.WriteLine("OK!"); else Console.WriteLine("wow!"); В этом случае на консоль будет выведено "OK!"

  • Anonymous
    November 10, 2008
    Старенький тест по C#, но вопросы интересные (с такими же подвохами) http://sharptest.narod.ru/

  • Anonymous
    November 11, 2008
    по 8-й задаче - пример был бы правилен если бы написали ++j. По бонусу - думаю будет wow.

  • Anonymous
    November 11, 2008
    ++j сработало бы, поскольку ++j возвращает значение переменной после инкремента. Это верно.

  • Anonymous
    November 12, 2008
    Gaidar a tvou kollekciu ti mog bi opublikovat ili na mail slit? Ya dumau bilo bi ochen polezno znat "C# hidden points".

  • Anonymous
    November 12, 2008
    Код не получится скомпилировать. т.к. нельзя неявно преобразовать int к byte

  • Anonymous
    November 13, 2008
    У меня коллекция достаточно разрозненная и примеры специфические. Со временем что-нибудь буду приводить в порядок и выкладывать. Либо освещать в постах связанных с решением тех или иных задач, поскольку в основном "приколы" связаны на с самим языком, а с платформой .NET и особенностями реализации некоторых компонентов.

  • Anonymous
    November 14, 2008
    Денис, это видимо и был ответ :)

  • Anonymous
    November 15, 2008
    > #7 ... Тут все просто: вызывается конструктор A, затем конструктор B, но т.к. не определено значение A.x, то для используется 0 в конструкторе B. А почему вы решили, что сначала вызывается конструктор A, а потом B? Внимательно читаем C# Language Specification, 10.5.5.1 "Static field initialization": "The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. If a static constructor exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class." В нашем случае static constructor'в нет, поэтому "... executed at an implementation-dependent time prior to the first use of a static field of that class". Без какой-либо гарантии касательно порядка выполнения.

  • Anonymous
    November 15, 2008
    Конструктор A вызывается раньше, поскольку к A происходит обращение в вызывающие коде раньше, чем к B.

  • Anonymous
    November 16, 2008
    А это не имеет никакого значения в данном случае. Читаем еще раз: "... the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class." "Prior" - не "непосредственно перед обращением", а вообще перед обращением. Это может вообще произойти при загрузке сборки. И это может произойти, даже если в коде не будет ни одного обращения к полям A и B. Разумеется, если инициализация произойдет до реального использования A и B, то рантайм не будет смотреть, в каком там порядке потом используются значения. Он просто проинициализирует поля в каком-то удобном ему (т.е. - undefined для нас) порядке. В частности, в приведенном примере возможна ситуация, когда сначала инициализируется B, потом A, а потом уже тот класс, в котором определен Main (и только потом начинается собственно выполнение тела Main). Вот здесь про это немного подробнее расписано у Джона Скита: http://www.yoda.arachsys.com/csharp/beforefieldinit.html

  • Anonymous
    November 16, 2008
    Сдается мне, что в приведенном примере инициализация будет выполняться именно в описанном мною порядке, поскольку код простой и нет предпосылок к оптимизации путем предварительной инициализации статических полей. Т.е. для public class A { public static int x = B.y + 1; } public class B { public static int y = A.x + 1; } static void Main(string[] args) {    Console.WriteLine("A.x = " + A.x);    Console.WriteLine("B.y = " + B.y); } Будет выведено: A.x = 2 B.y = 1 static void Main(string[] args) {    int dummy = B.y;    Console.WriteLine("A.x = " + A.x);    Console.WriteLine("B.y = " + B.y); } А вот если раньше обратиться к B, тогда можно получить другой результат: A.x = 1 B.y = 2

  • Anonymous
    November 16, 2008
    > Сдается мне, что в приведенном примере инициализация будет выполняться именно в описанном мною порядке В текущей имплементации C# от MS - да, скорее всего, так и есть. Но это не гарантируется ничем - ни спекой языка, ни документацией implementation-defined фич на сам Visual C#. Поэтому полагаться на это довольно-таки неразумно. И, кстати, я отнюдь не уверен, что даже на текущей реализации стоит это делать. Имхо, "преждевременная" инициализация переменных, скорее, наоборот несколько упрощает жизнь рантайму (не зря же они оговорили ее возможность, и ввели маркер beforefieldinit!). Поэтому вполне можно предположить, что рантайм будет пользоваться этой возможностью...

  • Anonymous
    November 16, 2008
    Вот пример кода, на котором инициализация идет в обратном порядке, хотя обращение к полям класса в рантайме просходит в том же, что и раньше: using System; class A { public static int x = B.y + 1; } class B { public static int y = A.x + 1; } class Program { static void Main() { Console.WriteLine("Main"); //Console.WriteLine("A.x = " + A.x); //Console.WriteLine("B.y = " + B.y); for (int i = 0; i < 2; ++i) if (i == 0) Console.WriteLine("A.x = " + A.x); else Console.WriteLine("B.y = " + B.y); } } Если посмотреть, что реально происходит, то оказывается, что вызов статических конструкторов JIT'ом вставлен в начало Main, и порядок их вызова как-то определяется кодом в теле метода, но не совсем понятно, как именно (во всяком случае - даже не порядком обращения к полям в тексте программы, как видно из примера выше - в нем по тексту сначала дергается A, потом B). Вот здесь еще про это: http://blogs.msdn.com/davidnotario/archive/2005/02/08/369593.aspx

  • Anonymous
    November 16, 2008
    Спасибо за интересную информацию! Я абсолютно с Вами согласен, что полагаться на порядок инициализации в данном случае не стоит. Как и вообще не стоит использовать циклическую инициализацию, поскольку это верная дорога к сложным для поиска багам.

  • Anonymous
    November 17, 2008
    Да и вообще, наверное, не стоит использовать ни один из приведенных примеров в реальной жизни, поскольку они все - "верная дорога к сложным для поиска багам". IMHO...

  • Anonymous
    November 23, 2008
    с такого исходного кода будет ошибка компиляции, и следовательно выведено не будет нечего. Усли же привети сумму к типу байт то Будет выведено ОК!, так как 3 то больше чем 1, и оба эти числа в байт умещается.