📜 ⬆️ ⬇️

6 C # Simple Trick Questions

Having read 10 simple tasks on c # with a trick, I was upset because in essence, there were no particular tricks there (this way you can roll up to "what will be the same i ++ + ++ i") ... Therefore, I decided to recall some tricks that I never wanted to see in my life 8-). The level of preparation of the middle probably.


Denial of responsibility


Much can be a terrible accordion. Of course, the examples are not mine, but I have studied (and the result below) (if the authors (they are not known to me often) want to mention them as pioneers - write in a personal - I will update the post).
Remember that the programmer wrote - I tried to convey some essence to those who read, but the language is dry and boring. And of course this is just what came to mind in 20 minutes.

And of course this is a holivarny topic, rather even a note so that “the brains do not swell.” There are no pictures of kittens here.
')
Ready? Then let's go ...

Task 1


What will be displayed on the screen?
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"); } } 

Details and answer
It turns out WW .
You can try to run and check.

The essence of this phenomenon is described in the "7.6.4.1 Identical simple names and type names" specification. To begin, I will give this section in its entirety:
If it is a simple identifier (§7.6.2), it can be defined as a single identifier as a type of name (§3.8), then both possible meanings of E are permitted. It is never ambiguous, since it’s in both cases. In other words, it would be a rule. 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 } } 

The color field is not defined.

Briefly, the essence of what is happening can be described like this: in the case when the variable name matches the type name, the compiler will try to find static members or subclasses with a name (in the type definition) specified after the dot, if the variable does not contain members with that name. If not found, it will produce a compilation error, of course.
It is this rule that gives this effect: if XmlReader has no Flush () method ( unlike XmlWriter ), the compiler outputs the delegate type in both cases as an Action <XmlWriter> and calls the appropriate suitable method, which W displays.


Task 2


Is it possible to create a program that uses await for a method that returns not a Task ?

Details and answer
Yes you can. And there are several ways to implement this.
If we recall that when meeting the word await, the compiler uses duck typing to search for candidates for calls in the generated state machine, the task will result in a simple selection of the necessary conditions.

Immediately you can build the following implementation:
 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(); } } 


And you can remember that the rule allows the use of extension methods for the same purposes and write so:
 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(); } } 



Task 3


Rather, a practical task that confuses some than a task with surprises.
Is it possible to "teach" asynchronous old (.net2) classes that implement the IAsyncResult * pattern without changing their code?
*) the IAsyncResult pattern implies the presence of a pair of methods of the form:
 IAsyncResult BeginXXX(AsyncCallback callback); void EndXXX(IAsyncResult); 

that perform some operation asynchronously. Read more in MSDN.
**) it is assumed that the caller, of course, is compiled into versions where async \ await is already supported.
Details and answer
Yes, you can write a class extension (in the new version) and use TaskCompletionSource for execution (or Task.Factory.FromAsyncPattern , but this is not exactly the same thing).

For example:
 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) { } } } 



Task 4


What will be on the screen when building in release?
What will be on the screen when building in release and running under debug?
  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"); } } } 


Details and answer
The bottom line is that it is not necessary in JIT to be able to find the scope of a variable inside a method and, as a result, destroy it by GC.
Therefore, a clear answer to this question can not be.
For example, in the Misrosoft JIT implementation under Windows (client JIT) in the .net4 version, this feature is implemented as follows:
- in the release assembly running without debug, these very “regions” are used,
- in a release assembly running under debagging, the scope is extended to the end of the method.
For example, on .net4.5 without debugging (when building in release mode) you will get [ctor, dtor, not null], as opposed to debugging the same build: [ctor, not null] (Can't see the catch? Think about that what was derived).
The code created by JIT is also different:
X86 assembler details
  >>> 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 

vs
 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 


However, this information may be incorrect - the versions may change, as well as the JIT settings on each specific system (there are no full sources).
In general, the problem (and its roots) are described by Sergey Teplyakov ) in a blog.


Task 5


What is j?
 Int32 i = Int32.MinValue; Int32 j = -i; 

Details and answer
Compiling in the checked context will give an exception. In unckecked we get the value Int32.MinValue . Just because sign types work this way.
Let me remind you that the most significant bit there means a sign. For example, for byte 127 + 1 = 128, 128 = 0x80 and in the sign representation it is -128.
Or in bits:
-128 = 1000 0000
127 = 0111 1111
-1 = 1111 1111
remembering the rules of multiplication we get the result.


Task 6


Is it possible to "scratch" memory in C # that you did not allocate *?
*) well or Is it possible to change the size (but not the allocated memory) of an already created array?
Details and answer
Well, actually you can.
  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; } } 

Similarly, you can turn other tricky tricks, for example with strings - the main thing is to know how they work internally, and windbg will help you easily with this.

Source: https://habr.com/ru/post/209008/


All Articles