⬆️ ⬇️

Interesting questions on knowledge of C # and .NET mechanisms

I suggest you a series of questions on C # and .NET as a whole, which can be useful for interviewing or simply help to better understand how the .NET platform works. Here there will be no usual questions about the difference between reference types and meaningful types. I tried to choose the most interesting ones that are worth considering.



  1. It is known that when placing an object of reference type on a heap, it has a pointer to an object-type (a memory area containing static fields and an implementation of static methods). This type object contains the index of the synchronization block and another pointer to the type object. Why is it needed and where it points?



    Answer
    In the CLR, every object on the heap has a pointer to a type object. This is necessary, for example, to find the values ​​of static fields and the implementation of static methods for an instance of a type. But the type object referenced by the type instance also has a reference to the type object and is an “instance” for the System.Type type object, the type object for which the CLR is created on startup.



    In this diagram, the Manager object refers to the Manager type object, the pointer to which object type refers to the System.Type type object.



  2. Is it possible to declare a delegate not only inside the class, but also in the global scope? Why?



    Answer
    Can. The delegate is not just a wrapper for the method, but a full-fledged class, and a class can be made both nested in the parent class and simply declared in the global scope. That is, a delegate can be defined wherever a class can be defined.

    ')

    internal class Feedback : System.MulticastDelegate { //  public Feedback(Object object, IntPtr method); // ,       public virtual void Invoke(Int32 value); // ,     public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object object); public virtual void EndInvoke(IAsyncResult result); } 


    Another interesting question is why the delegate class constructor contains two parameters, and in the code we simply pass a pointer to the method (internal for the CLR, according to which it will find this method)?



     delegate void Test(int value); void A(int v) { Console.WriteLine(v); } void TestDelegate() { var t = new Test(A); t(1); } 


    It's simple - because the compiler, when creating the delegate itself, substitutes the value of the parameter object for the constructor . If the method by which the delegate is initialized is static, null is passed. Otherwise, an object is passed to the instance of the class to which the method belongs. In this case, the state of this object can be changed through the this keyword within the method.



  3. A simple question is what the Test method displays and why?



     delegate int GetValue(); int Value1() { return 1; } int Value2() { return 2; } void Test() { var v1 = new GetValue(Value1); var v2 = new GetValue(Value2); var chain = v1; chain += v2; Console.WriteLine(chain()); } 


    Answer
    Displays 2. When delegates are placed in a chain at the delegate's chain, the internal field is filled in, which is an array of delegates (in case there are more than one number, otherwise the method reference is stored). All delegates are run sequentially. Returns the value of the latter, the rest are not taken into account.



  4. Explain how the local variables pass1 and pass2 from the Test method are passed to the lambda expression if the WaitCallback takes only one parameter (and in this case the reference to it is null ).



     namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var p = new Program(); p.Test(); Console.ReadKey(); } void Test() { int pass1 = 5; object pass2 = "Passing test"; ThreadPool.QueueUserWorkItem((obj) => { Console.WriteLine(pass1); Console.WriteLine(pass2); }); } } } 


    Answer
    In order to understand this, open the assembly in ildasm.

    You can make sure that in this case the lambda expression is not a method, but a whole class!



     .method private hidebysig instance void Test() cil managed { //  : 44 (0x2c) .maxstack 2 .locals init ([0] class ConsoleApplication1.Program/'<>c__DisplayClass1_0' 'CS$<>8__locals0') IL_0000: newobj instance void ConsoleApplication1.Program/'<>c__DisplayClass1_0'::.ctor() IL_0005: stloc.0 IL_0006: nop IL_0007: ldloc.0 IL_0008: ldc.i4.5 IL_0009: stfld int32 ConsoleApplication1.Program/'<>c__DisplayClass1_0'::pass1 IL_000e: ldloc.0 IL_000f: ldstr "Passing test" IL_0014: stfld object ConsoleApplication1.Program/'<>c__DisplayClass1_0'::pass2 IL_0019: ldloc.0 //    ! IL_001a: ldftn instance void ConsoleApplication1.Program/'<>c__DisplayClass1_0'::'<Test>b__0'(object) IL_0020: newobj instance void [mscorlib]System.Threading.WaitCallback::.ctor(object, native int) IL_0025: call bool [mscorlib]System.Threading.ThreadPool::QueueUserWorkItem(class [mscorlib]System.Threading.WaitCallback) IL_002a: pop IL_002b: ret } // end of method Program::Test 


    But the description of the class itself and it contains the method under discussion:



    The compiler determines whether the lambda expression contains references to local variables. If not, a static method is generated (or an instance method, if the lambda expression contains references to the members of the type instance). And if references to local variables are present, then a class is generated that contains the required fields and the method described in the lambda expression.



  5. What will the following code display?



     int a = -5; Console.WriteLine(~a); 


    Answer
    Displays 4. Operator ~ performs bitwise reversion.



     Console.WriteLine("{0:x8}, {1:x8}", -5, ~(-5)); //  fffffffb, 00000004 


    Moreover, for a value of 5 will display -6.



  6. Usually it is not recommended to manually control garbage collection. Why? Give an example when calling GC.Collect () makes sense.



    Answer
    The fact is that the garbage collector itself sets thresholds for generations (depending on the actual behavior of the application). As soon as the size of the generation in the managed pile exceeds the threshold, the garbage collection begins (this is described in great detail in Richter). Therefore, calls to GC.Collect () should most often be avoided. But it may be necessary to manually clean the garbage, if a one-time event occurred, which led to the destruction of many old objects . Thus, based on past behavior, the garbage collector predictions will not be accurate, and garbage collection will be most welcome.



  7. Job Interview Bonus: There is a rand2 method that yields 0 or 1 with the same probability. Write the rand3 method using the rand2 method, issuing 0,1,2 with the same probability.



    Answer
     //   int rand3() { int x, y; do { x = rand2(); y = rand2(); } while (x == 0 && y == 1); return x + y; } //   int rand3() { int r = 2 * rand2() + rand2(); if (r < 3) return r; return rand3(); } 




Any criticism is welcome. Questions are still on other topics, if interested.

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



All Articles