Compartilhar via


Задачки на C#

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

Я предлагаю сыграть в следующую игру - посмотрите на задачи, запишите решения пришедшие вам в голову (условие: не запускать и не компилировать программы!), а потом напишите в комментариях к сообщению количество правильных ответов (можно проверить запустив компилятор, а можно дождаться публикации решений - я сделаю это через несколько дней). Если не стесняетесь, то сразу напишите ваши предположения и объяснения по полученному результату.

Задачи я собирал давно, ниже приведены лишь самые простые, которые очень удобно показывать на слайдах презентации. Саму презентацию меня сподвигла сделать книга Java Puzzlers: Traps, Pitfalls, and Corner Cases, поскольку часть задачек оказалась похожими как две капли воды. Кстати, не может не радовать, что многие проблемные моменты Java, на которых строятся задачи из книги, в C# просто отсутствуют :)

Если у вас есть интересные задачи на 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;
}

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

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

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

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

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

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

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

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

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

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

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);
}

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

int j = 0;

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

Console.WriteLine(j);

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

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

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

Console.WriteLine(counter);

10. А такого?

float begin = 1000000000;
int counter = 0;

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

Console.WriteLine(counter);

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); }

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

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

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

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; });

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

А)

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

Б)

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

В)

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

Comments

  • Anonymous
    October 26, 2008
  1. OverflowException?
  2. Naan или как там обозначается значение бесконечности.
  3. Перегружен опретор Equals? Происходит не ReferenceEquals?
  4. Код символа 'а'.
  5. "Существует"
  6. "ABC" и число, сумма кодов символов A, B, C.
  7. Какой-нибудь StackOverflowException?
  8. 10
  9. 101
  10. 1000000010
  11. B
  12. int[]
  13. 12345
  14. Ничего
  15. A) Hello Goodbye    Б) Hello Goodbye    В) Hello
  • Anonymous
    October 26, 2008
    NaN и infinity это разные значения

  • Anonymous
    October 26, 2008
    Если нужны подобные "головоломки" то советую заглянуть на http://rsdn.ru/Forum/?group=dotnet и поискать темы начатые nikov. Что то типа http://rsdn.ru/Forum/Message.aspx?mid=3106078&only=1

  • Anonymous
    October 26, 2008
    В шестом вопросе ошибка в слове "следуюет". Поправьте. Свои ответы записал на бумажке. Жду правильных ответов )))

  • Anonymous
    October 26, 2008
    1 Задача! Ужас! Посмотрел код , результатом работы программы должно быть число 10, то что компилятор откажется это компилировать я даже и не подозревал, но почему? Ведь long прнимает числа от –9,223,372,036,854,775,808 до9,223,372,036,854,775,807 ! Проблему решил так : static void Main(string[] args)  {            Console.WriteLine(GetSomeResult(10000));            Console.Read();  }  static long GetSomeResult(long someValue)  {        long value1 =  10 * 1000 * 10000 * someValue;         long value2 = 10 * 1000 * 10000 * someValue*10;         return value2 / value1;   } 2 Задача Представления не имею, но скорей всего какое то выражение!

  1. Задача public static void Main() {    Test t = new Test();    Console.WriteLine(t.Equals(t)); }  public class Test  {     public Test()      {      }  } У меня возвращает true; 4 Задача. Не знаю!
  2. Задача Не знаю!
  3. Задача  static void Main(string[] args)        {            Console.WriteLine("A" + "B" + "C");           Console.WriteLine('A' + 'B' + 'C');            Console.Read();        } Ответ: ABC и 198 Проверил так : static void Main(string[] args)        {            Console.WriteLine("A" + "B" + "C");            Console.WriteLine((int)'A');//+            Console.WriteLine((int)'B');//+            Console.WriteLine((int)'C');//+            Console.WriteLine('A' + 'B' + 'C');//=            Console.Read();        }
  4. Задача 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.x , компилятор пытается вычислить A.x по формуле B.y+1; но B.y равен A.x+1, т.к A.x на данный момент не вычеслен, а по умолчанию переменные класса типа int если они не заданы инициализируются 0 , значит формула A.x+1 превращается в 0+1 тем самым B.y=1, далее формула B.y+1 превращается в 1+1 тем самым A.x=2; А вот дальше не понятно так как A.x=2 то формула A.x+1 должна вернуть 3 , но y-ку присваиваится почему то 1,  видимо потому что компилятор думает что A.x всё ещё не вычеслен и по умолчанию инициализирован  0-ём.
  5. Задача int j = 0; for (int i = 0; i < 10; i++)    j = j++; Console.WriteLine(j);   Парадокс. Результат 0. Если разбить код  то получается следующее: Стока  j = j++; превращается в следующие две j=j и j=j+1; и так 10 раз. //////////////////////////////// int j=0; Цикл(10раз) j = 0;        //1 j = j + 1;   //1 j = 1;        //2 j = 1 + 1;   //2 j = 2;        //3 j = 2 + 1;  //3 j = 3;        //4 j = 3 + 1;  //4 j = 4;        //5 j = 4 + 1;  //5 j = 5;        //6 j = 5 + 1;  //6 j = 6;        //7 j = 6 + 1;  //7 j = 7;        //8 j = 7 + 1;  //8 j = 8;        //9 j = 8 + 1;  //9 j = 9;       //10 j = 9 + 1;  //10 ///////////////////////////////////////// А вот так работает int j = 0; for (int i = 0; i < 10; i++)    j = j++; Console.WriteLine(j); или так int j = 0; for (int i = 0; i < 10; i++)    j +=1 ; Console.WriteLine(j);
  6. Задача int end = int.MaxValue; int begin = end - 100; int counter = 0; for (int i = begin; i <= end; i++)    counter++; Console.WriteLine(counter); Я в шоке!!!! Сначала всё нормально, цикл доходит до 2147483647 и вместо того чтобы остановиться начинает работать в обратную сторону на уменьшение i-того. Жесть!
  7. Задача ! Не разбирался, мне 9-ой хватило. 11.Задача  Где то я уже с таким приколом встречался! Интересно что в Microsoft говорят.
  8. Задача. То же самое что и 11 задача
  • Anonymous
    October 26, 2008
  1. Ошибка компиляции. 10 * 1000 * 10000 * * 100000 - здесь все литералы имеют тип int, их произведение, соответственно, тоже, а overflow в констатных выражениях всегда проверяется на этапе компиляции.
  2. float.NaN
  3. Либо в Test так переопределен Equals(), либо это структура с float или double полем, в котором NaN.
  4. ((int)'a').ToString()
  5. Всегда будет "Существует". Тут проблема с приоритетами операций - сначала посчитается значение выражения ("Элемент test " + col["test"]), которое, разумеется, никогда не будет null. Потом оно сравнится с null (результат - всегда false). И уже потом этот результат используется в операторе ?: в качестве условия.
  6. "ABC", затем ((int)'A' + (int)'B' + (int)'C').ToString()
  7. В данном виде - порядок инициализации не определен, т.к. у классов нет статических конструкторов, и, как следствие, на них будет стоять beforefieldinit - а значит, рантайм имеет право инициализировать любой из них в любой момент (и начать как с A, так и с B). Если конструкторы добавить, то для данного кода будет "A.x = 2", "B.y = 1".
  8. "0", т.к. в C# строго определен порядок вычисления подвыражений. В данном случае сначала выполняется оператор ++, потом =.
  9. Уйдем в бесконечную рекурсию, т.к. нет такого значения у int, для которого (x <= int.MaxValue) дало бы false - по определению MaxValue.
  10. Тоже рекурсия, но по другой причине. У float не хватит точности, чтобы запомнить 10 значащих цифр у числа. Поэтому произойдет округление, и (1000000000f + 1) == 1000000000f.  
  11. A.Test(int). Это вам не C++.
  12. Не скомпилируется - ambiguity.
  13. Вообще-то, не определено, т.к. порядок вызова предиката List<T>.FindAll не специфицирует. В общем случае, будут выведены все элементы коллекции, в произвольном порядке.
  14. "123". Enumerable.GroupBy ничего не сделает из-за "deferred execution". А вот ToLookup - не deferred.

A) Hello Goodbye (очевидно) B) Hello Goodbye (ThreadAbortException гарантирует выполнение finally) C) Hello Goodbye (Environment.Exit гарантирует выполнение finally, в отличие от FailFast).

  • Anonymous
    October 26, 2008
    Как я посмотрю, задачки не только на C#, но и на FCL. В таком случае, вот вам еще: class Foo : IComparable<Foo> {  public int x;  public int CompareTo(Foo other) {    return other == null ? -1 : x.CompareTo(other.x);  }  public override string ToString() { return x.ToString(); } } Foo[] a = { null, new Foo { x = 1 }, new Foo { x = 2 } }; Array.Sort(a); Console.WriteLine("{0}|{1}|{2}", a[0], a[1], a[2]); что выведется, и почему?

  • Anonymous
    October 26, 2008
    to int19 По задаче №8 С чего это в конструкции j=j++; первым выполнится ++ , а потом уже присваивание.Если бы было j=++j; тогда да,   даже если и так почему результат равен 0?  

  • Anonymous
    October 26, 2008

  1. Ошибка приведения типов будет в этом случае... результат операции деления в этой ситуации должен приводится к long явно.
  2. Да абсолютно любое. Сравнение чисел с плавающей запятой никогда true не дает. 3.Здесь ссылочный тип, а значит сравниваться будут ссылки на объекты, а в этой ситуации (боюсь ошибиться) они будут различны.
  3. Приведение типов будет, т.е. на экране будет код символа 'a'. Во-первых, компилятор уберет b, т.к. выражение тут всегда верное, а значит будет типа такого: WriteLine(a)... но т.к. была переменная с типом int, то будет приведение (int)а, т.к. если я не ошибаюсь, то к типу int компилятор в таких ситуацию приводит почти все если может. А char он привести может.
  4. Это равносильно "Элемент test"+null или Строка+ничего будет строка... а в этой ситуации строка никогда null не будет, а значит всегда будет "Существует!"
  5. Выше уже отвечали, я этого же варианта придерживаюсь. Просто компилятор посчитает сумму 'A'+'B'+'C', как число, а не как строку.
  6. не знаю
  7. Ноль будет, там операция. Вот почему... сначало будет вычисляться правая часть выражения... а значит в стек, в этой ситуации, будет запихнуто значение 0, а потом j будет увеличино на 1 и запихнуто снова с стек... после этого, и стека в переменную j вывалится 1, а потом для j (левой части), извлечется 0, т.к. он там был перед еденицей... поэтому всегда в этом выражении будет 0. Что бы выражение было верным, нужно либо исключить левую часть выражения, либо сделать операцию ++ префиксной. ладно времени больше на анализ нету... :)
  • Anonymous
    October 27, 2008
    to seagul2000 Потому что
  1. Вычисляется j (j=0)
  2. Вычисляется j++ (j=1)
  3. j присваивается результат вычисления j (j=0) Кстати в С++ все не так просто :)
  • Anonymous
    October 27, 2008
    Потому, что операторы выполняются в порядке приоритетов, и слева направо. Приоритет у оператора ++ выше, чем у =. Причем и у префиксной, и у постфиксной версии. Соответственно - сначала выполняется постфиксный ++. Он увеличивает значение переменной j, и возвращает ее значение до увеличения (по определению данного оператора) - но после его выполнения (и до присваивания) значение переменной уже увеличено. А потом возвращенное этим оператором значение - т.е. j до увеличения - снова присваивается в j. Таким образом, все выражение вместе - это NOP, и j будет всегда равен тому, что в него присвоили сначала - т.е. 0.

  • Anonymous
    October 27, 2008
    to int19h Спасибо, разобрался! Т.е получается в виде псевдо-кода так: Конструкция j=j++: int j=0; 1)Выполняется j++ 2)но после выполнения j++; который увеличил j на 1 , j-ому присваивается 0, и так 10 раз;

  • Anonymous
    October 28, 2008
    to int19h: "3. Либо в Test так переопределен Equals(), либо это структура с float или double полем, в котором NaN." а где есть структуры с конструктором без параметров...

  • Anonymous
    October 28, 2008
    И что же получается, префиксные и постфиксные операторы работают совершенно одинаково?

  • Anonymous
    October 29, 2008
    > Да абсолютно любое. Сравнение чисел с плавающей запятой никогда true не дает. Вот это неправда. Числа с плавающей запятой как таковые замечательно сравниваются, проблемы при сравнении начинаются тогда, когда сравнивают результаты вычислений с применением таких чисел (из-за ошибок округления). Но если сравнивать два числа, созданные из литералов, то все OK: Trace.Assert(1.0 == 1.0); Более того, наличие ошибок округления зависит от того, какие именно операции производятся. Далеко не всякая операция вообще может задействовать округление. В общем, всем курить: http://docs.sun.com/source/806-3568/ncg_goldberg.html