Welcome to MSDN Blogs Sign in | Join | Help

Гайдар Магдануров

Платформа и инструменты разработки, новости компании Microsoft и мысли об ИТ
Задачки на 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!");
Posted: Sunday, October 26, 2008 11:27 PM by gaidar
Filed under: ,

Comments

derigel said:

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

# October 27, 2008 3:21 AM

DigitalDevil said:

NaN и infinity это разные значения

# October 27, 2008 3:27 AM

hdd said:

Если нужны подобные "головоломки" то советую заглянуть на http://rsdn.ru/Forum/?group=dotnet и поискать темы начатые nikov.

Что то типа http://rsdn.ru/Forum/Message.aspx?mid=3106078&only=1

# October 27, 2008 3:53 AM

Tehnolog said:

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

# October 27, 2008 3:54 AM

seagul2000 said:

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 Задача

Представления не имею, но скорей всего какое то выражение!

3. Задача

public static void Main()

{

   Test t = new Test();

   Console.WriteLine(t.Equals(t));

}

 public class Test

 {

    public Test()

     {

     }

 }

У меня возвращает true;

4 Задача. Не знаю!

5. Задача Не знаю!

6. Задача

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

       }

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.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-ём.

8. Задача

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

9. Задача

int end = int.MaxValue;

int begin = end - 100;

int counter = 0;

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

   counter++;

Console.WriteLine(counter);

Я в шоке!!!! Сначала всё нормально, цикл доходит до 2147483647 и вместо того чтобы остановиться начинает работать в обратную сторону на уменьшение i-того. Жесть!

10. Задача ! Не разбирался, мне 9-ой хватило.

11.Задача  Где то я уже с таким приколом встречался! Интересно что в Microsoft говорят.

12. Задача. То же самое что и 11 задача

# October 27, 2008 3:57 AM

int19h said:

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.

15.

A) Hello Goodbye (очевидно)

B) Hello Goodbye (ThreadAbortException гарантирует выполнение finally)

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

# October 27, 2008 6:59 AM

int19h said:

Как я посмотрю, задачки не только на 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]);

что выведется, и почему?

# October 27, 2008 7:09 AM

seagul2000 said:

to int19

По задаче №8

С чего это в конструкции j=j++; первым выполнится ++ , а потом уже присваивание.Если бы было j=++j; тогда да,   даже если и так почему результат равен 0?  

# October 27, 2008 7:23 AM

Калдузов Алексей said:

1. Ошибка приведения типов будет в этом случае... результат операции деления в этой ситуации должен приводится к long явно.

2. Да абсолютно любое. Сравнение чисел с плавающей запятой никогда true не дает.

3.Здесь ссылочный тип, а значит сравниваться будут ссылки на объекты, а в этой ситуации (боюсь ошибиться) они будут различны.

4. Приведение типов будет, т.е. на экране будет код символа 'a'. Во-первых, компилятор уберет b, т.к. выражение тут всегда верное, а значит будет типа такого: WriteLine(a)... но т.к. была переменная с типом int, то будет приведение (int)а, т.к. если я не ошибаюсь, то к типу int компилятор в таких ситуацию приводит почти все если может. А char он привести может.

5. Это равносильно "Элемент test"+null или Строка+ничего будет строка... а в этой ситуации строка никогда null не будет, а значит всегда будет "Существует!"

6. Выше уже отвечали, я этого же варианта придерживаюсь. Просто компилятор посчитает сумму 'A'+'B'+'C', как число, а не как строку.

7. не знаю

8. Ноль будет, там операция. Вот почему... сначало будет вычисляться правая часть выражения... а значит в стек, в этой ситуации, будет запихнуто значение 0, а потом j будет увеличино на 1 и запихнуто снова с стек... после этого, и стека в переменную j вывалится 1, а потом для j (левой части), извлечется 0, т.к. он там был перед еденицей... поэтому всегда в этом выражении будет 0. Что бы выражение было верным, нужно либо исключить левую часть выражения, либо сделать операцию ++ префиксной.

ладно времени больше на анализ нету... :)

# October 27, 2008 7:33 AM

aek239 said:

to seagul2000

Потому что

1. Вычисляется j (j=0)

2. Вычисляется j++ (j=1)

3. j присваивается результат вычисления j (j=0)

Кстати в С++ все не так просто :)

# October 27, 2008 10:37 AM

int19h said:

Потому, что операторы выполняются в порядке приоритетов, и слева направо. Приоритет у оператора ++ выше, чем у =. Причем и у префиксной, и у постфиксной версии.

Соответственно - сначала выполняется постфиксный ++. Он увеличивает значение переменной j, и возвращает ее значение до увеличения (по определению данного оператора) - но после его выполнения (и до присваивания) значение переменной _уже_ увеличено. А потом возвращенное этим оператором значение - т.е. j до увеличения - снова присваивается в j. Таким образом, все выражение вместе - это NOP, и j будет всегда равен тому, что в него присвоили сначала - т.е. 0.

# October 28, 2008 1:21 AM

seagul2000 said:

to int19h

Спасибо, разобрался!

Т.е получается в виде псевдо-кода так:

Конструкция j=j++:

int j=0;

1)Выполняется j++

2)но после выполнения j++; который увеличил j на 1 , j-ому присваивается 0, и так 10 раз;

# October 28, 2008 2:51 AM

Iurgen said:

to int19h:

"3. Либо в Test так переопределен Equals(), либо это структура с float или double полем, в котором NaN."

а где есть структуры с конструктором без параметров...

# October 29, 2008 3:46 AM

srgb said:

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

# October 29, 2008 4:40 AM

int19h said:

> Да абсолютно любое. Сравнение чисел с плавающей запятой никогда true не дает.

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

Trace.Assert(1.0 == 1.0);

Более того, наличие ошибок округления зависит от того, какие именно операции производятся. Далеко не всякая операция вообще может задействовать округление.

В общем, всем курить:

http://docs.sun.com/source/806-3568/ncg_goldberg.html

# October 30, 2008 4:20 AM
New Comments to this post are disabled
Page view tracker