Отказ от ответственности
Многое может оказаться жутким баяном. Конечно, примеры не мои, но мной изучены (и результат ниже) (если авторы (они мне и не известны часто) хотят упоминания их как первооткрывателей — пишите в личку — обновлю пост).
Помните что писал программист — я попытался донести некоторую суть до тех кто прочитает, но язык сух и скучен. Ну и конечно это лишь то что пришло в голову минут за 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.
**) подразумевается что вызывающий, конечно, компилируется в версии, где async\await уже поддерживаются.
К примеру:
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.
ссылка на оригинал статьи http://habrahabr.ru/post/209008/
Добавить комментарий