Хочу представить вашему вниманию комические купле каверзные вопросы по 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();
}
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();
}
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();
}
Здесь все чуть более очевидно по сравнению с прошлым примером.
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 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;
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;
}
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 раз. Причем была взята ссылка на переменную 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 выполнен");
}
}
Метод 2
Блок if выполнен не будет, так как SomeMethod1 возвращает false. Но, так как используется логический оператор &, то будет проверено и второе условие – SomeMethod2. Если бы использовался более привычный оператор &&, то проверено было бы только значение первого метода.
9 Еще один простой (даже можно сказать бородатый) вопрос. Что будет результатом выполнения следующего кода?
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o;
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());
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;
На всякий случай коротенько о различиях между 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)
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 Что такое дословные/точные строки (verbatim strings)?
Verbatim strings — это строки, которые начинаются с символа @ и в которых не обрабатываются escape-последовательности.
Как их правильно называть на русском — дословные или точные это уже отдельный вопрос.
Автор: asommer