Почитав 10 простых задач на c# с подвохом я огорчился т.к. по сути своей там и подвохов-то не было особо (этак можно скатиться до "чему будет равно i++ + ++i")… Посему решил немного повспоминать подвохи, которые не хотел бы видеть никогда в жизни 8-). Уровень подготовки middle наверно.
Отказ от ответственности
Многое может оказаться жутким баяном. Конечно, примеры не мои, но мной изучены (и результат ниже) (если авторы (они мне и не известны часто) хотят упоминания их как первооткрывателей — пишите в личку — обновлю пост).
Помните что писал программист — я попытался донести некоторую суть до тех кто прочитает, но язык сух и скучен. Ну и конечно это лишь то что пришло в голову минут за 20.
Ну и конечно это холиварный топик, скорее даже заметка чтобы "не отекли мозги". Тут даже фотки котяток нет.
Готовы? Тогда поехали...
Задача 1
Что будет выведено на экран?
using System;
using System.Xml;
public class Program
{
public static void Main()
{
Bar(XmlWriter => XmlWriter.Flush());
Bar(XmlReader => XmlReader.Flush());
}
private static void Bar(Action<XmlWriter> x)
{
Console.WriteLine("W");
}
private static void Bar(Action<XmlReader> x)
{
Console.WriteLine("R");
}
}
Можете попробывать запустить и проверить.
Суть этого явления описана разделе "7.6.4.1 Identical simple names and type names" спецификации. Для начала я приведу этот раздел целиком:
In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.6.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred. For example:
struct Color
{
public static readonly Color White = new Color(...);
public static readonly Color Black = new Color(...);
public Color Complement() {...}
}
class A
{
public Color Color; // Field Color of type Color
void F() {
Color = Color.Black; // References Color.Black static member
Color = Color.Complement(); // Invokes Complement() on Color field
}
static void G() {
Color c = Color.White; // References Color.White static member
}
}
Within the A class, those occurrences of the Color identifier that reference the Color type are underlined, and those that reference the Color field are not underlined.
Кратко суть происходящего можно описать примерно так: компилятор в случае когда имя переменной совпадает с именем типа будет пытаться найти статические члены или субклассы с именем (в определении типа), заданным после точки, если переменная не содержит членов с таким именем. При ненахождении выдаст ошибку компиляции, конечно.
Именно это правило и даёт этот эффект: т.к. у XmlReader-а нет метода Flush() (в отличии от XmlWriter), то компилятор выводит тип делегата в обоих случаях как Action<XmlWriter> и вызывает соотвествующий подходящий метод, который и выводит W.
Задача 2
Можно ли создать программу, где используется await для метода, который не помечен как async?
Конечно речь не идёт о «не своих» классах.
Если вспомнить что при встрече слова await компилятор использует утиную типизацию для поиска кандидатов для вызовов в генерируемом конечном автомате, то задача выльется в простой подбор нужных условий.
Сходу можно соорудить такую реализацию:
using System.Runtime.CompilerServices;
class Program
{
private static void Main()
{
MainAsync();
}
private async static void MainAsync()
{
await Foo();
}
static Target Foo()
{
return new Target();
}
}
class Target
{
public TaskAwaiter GetAwaiter()
{
return new TaskAwaiter();
}
}
А можно вспомнить, что правило допускает использование методов расширений для тех же целей и написать так:
using System.Runtime.CompilerServices;
class Program
{
private static void Main()
{
MainAsync();
}
private async static void MainAsync()
{
await Foo();
}
static Target Foo()
{
return new Target();
}
}
class Target
{
}
static class TargetEx
{
public static TaskAwaiter GetAwaiter(this Target t)
{
return new TaskAwaiter();
}
}
Задача 3
Скорее практическая задача, которая некоторых ставит в тупик, чем задача с внезапностями.
Можно ли "научить" асинхронности старые (.net2) классы, которые имплементируют паттерн IAsyncResult* без изменения их кода?
*) под паттерном IAsyncResult подразумевается наличие пары методов вида:
IAsyncResult BeginXXX(AsyncCallback callback);
void EndXXX(IAsyncResult);
, которые осуществляют выполнение некоторой операции асинхронно. Прочитать подробнее в MSDN.
**) подразумевается что вызывающий, конечно, компилируется в версии, где asyncawait уже поддерживаются.
К примеру:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static void Main()
{
MainAsync();
Console.ReadLine();
}
private async static void MainAsync()
{
var ogc = new OldGoodClass();
await ogc.OperationAsync().ConfigureAwait(false);
}
}
static class OldGoodClassEx
{
public static Task OperationAsync(this OldGoodClass ogc)
{
var tsc = new TaskCompletionSource<object>(ogc);
AsyncCallback onDone = (ar) =>
{
ogc.EndOperation(ar);
tsc.SetResult(null);
};
ogc.BeginOperation(onDone);
return tsc.Task;
}
}
class OldGoodClass
{
class AsyncResult : IAsyncResult
{
#region Implementation of IAsyncResult
public bool IsCompleted { get; set; }
public WaitHandle AsyncWaitHandle { get; set; }
public object AsyncState { get; set; }
public bool CompletedSynchronously
{
get { return false; }
}
#endregion
}
public IAsyncResult BeginOperation(AsyncCallback onDone)
{
var rv = new AsyncResult();
ThreadPool.QueueUserWorkItem(s =>
{
Thread.Sleep(2000);
var ar = (AsyncResult) s;
ar.IsCompleted = true;
if (onDone != null) onDone(ar);
}, rv);
return rv;
}
public void EndOperation(IAsyncResult r)
{
while (!r.IsCompleted) { }
}
}
Задача 4
Что будет на экране при сборке в release?
Что будет на экране при сборке в release и запуске под дебагом?
using System;
internal class Program
{
private class MyClass
{
public MyClass()
{
Console.WriteLine("ctor");
GC.Collect();
GC.WaitForPendingFinalizers();
}
~MyClass()
{
Console.WriteLine("dtor");
}
}
private static void Main(string[] args)
{
var myClass = new MyClass();
if (myClass != null)
{
Console.WriteLine("not null");
}
else
{
Console.WriteLine("null");
}
}
}
Поэтому однозначного ответа на этот вопрос не может быть.
Так например, в реализации JIT-а Misrosoft под Windows (клиентский JIT) в версии .net4 эта возможность реализована так:
— в release сборке, запущенной без дебага, используются эти самые «регионы»,
— в release сборке, запущенной под дебагом, область видимости продлевается до конца метода.
Например на .net4.5 без дебага (при сборке в release режиме) будет выдано [ctor, dtor, not null], в отличие от под дебагом той же сборки: [ctor, not null] (Не видите подвох? Вдумайтесь в порядок того что вывелось).
Код, созданный JIT-ом также разный:
>>> 002800D8 55 push ebp
002800D9 8BEC mov ebp,esp
002800DB 83EC0C sub esp,0Ch
002800DE 33C0 xor eax,eax
002800E0 8945F4 mov dword ptr [ebp-0Ch],eax
002800E3 894DFC mov dword ptr [ebp-4],ecx
002800E6 833D6031150000 cmp dword ptr ds:[00153160h],0
002800ED 7405 je 002800F4
002800EF E83A796362 call 628B7A2E (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
002800F4 33D2 xor edx,edx
002800F6 8955F8 mov dword ptr [ebp-8],edx
002800F9 B918381500 mov ecx,153818h (MT: ConsoleApplication11.Program+MyClass)
002800FE E8C9833A62 call 626284CC (JitHelp: CORINFO_HELP_NEWFAST)
00280103 8945F4 mov dword ptr [ebp-0Ch],eax
00280106 8B4DF4 mov ecx,dword ptr [ebp-0Ch]
00280109 FF1538381500 call dword ptr ds:[00153838h] (ConsoleApplication11.Program+MyClass..ctor(), mdToken: 06000004)
0028010F 8B45F4 mov eax,dword ptr [ebp-0Ch]
00280112 8945F8 mov dword ptr [ebp-8],eax
00280115 837DF800 cmp dword ptr [ebp-8],0
00280119 7410 je 0028012B
0028011B 8B0D38213803 mov ecx,dword ptr ds:[03382138h] ("not null")
00280121 E8FACD6561 call 618DCF20 (System.Console.WriteLine(System.String), mdToken: 06000993)
00280126 90 nop
00280127 8BE5 mov esp,ebp
00280127 8BE5 интересно а кто-то вообще обращает внимание что тут написано?)
00280129 5D pop ebp
0028012A C3 ret
0028012B 8B0D3C213803 mov ecx,dword ptr ds:[0338213Ch] ("null")
00280131 E8EACD6561 call 618DCF20 (System.Console.WriteLine(System.String), mdToken: 06000993)
00280136 90 nop
00280137 8BE5 mov esp,ebp
00280139 5D pop ebp
0028013A C3 ret
против
002F0098 55 push ebp
002F0099 8BEC mov ebp,esp
002F009B B924381900 mov ecx,193824h (MT: ConsoleApplication11.Program+MyClass)
002F00A0 E827843362 call 626284CC (JitHelp: CORINFO_HELP_NEWFAST)
002F00A5 8BC8 mov ecx,eax
002F00A7 FF1544381900 call dword ptr ds:[00193844h] (ConsoleApplication11.Program+MyClass..ctor(), mdToken: 06000004)
002F00AD E892CE5E61 call 618DCF44 (System.Console.get_Out(), mdToken: 06000946)
002F00B2 8BC8 mov ecx,eax
002F00B4 8B1538217803 mov edx,dword ptr ds:[03782138h] ("not null")
002F00BA 8B01 mov eax,dword ptr [ecx]
002F00BC 8B403C mov eax,dword ptr [eax+3Ch]
002F00BF FF5010 call dword ptr [eax+10h]
002F00C2 5D pop ebp
002F00C3 C3 ret
Впрочем, эти сведения могут быть неверными — версии могут меняться, равно как и настройки JIT-а на каждой конкретной системе (исходников-то полноценных нет).
В целом проблема (и её корни) описаны у Сергея Теплякова) в блоге.
Задача 5
Чему равно j?
Int32 i = Int32.MinValue;
Int32 j = -i;
Напомню что старший бит там означает знак. Например для байта 127+1 = 128, 128 = 0x80 и в знаковом представлении это -128.
Или в битах:
-128 = 1000 0000
127 = 0111 1111
-1 = 1111 1111
вспоминая правила умножения получим результат.
Задача 6
Можно ли в C# "поковырять" память, которую вы не выделяли*?
*) ну или Можно ли поменять размер (но не выделенную память) уже созданного массива?
using System.Runtime.InteropServices;
class ArrayLength
{
public int Length;
}
[StructLayout(LayoutKind.Explicit)]
class MyArray
{
[FieldOffset(0)]
public ArrayLength ArrayLength;
[FieldOffset(0)]
public byte[] Array = new byte[4];
}
internal class Program
{
private static void Main(string[] args)
{
var arr = new MyArray();
arr.ArrayLength.Length = 1024;
}
}
Аналогично можно проворачивать и другие хитрые фокусы, например со строками — главное знать как они устроены внутренне, а с этим вам легко поможет windbg.
Автор: jonie