In the previous article, I analyzed the first 10 questions, and in this article I will try to analyze all the remaining questions.
Sisters:Analyze the 19 C# interview questions of the "60k" boss (Part 1)
These questions are indeed not used very often, so in the following text, I will propose a set of my private classic "6k interview questions" for everyone to relax.
Let’s take a look at the topic first:
11 Briefly describe the lazy computing mechanism of LINQ
12 Use SelectMany to implement the elements in two arrays as Cartesian sets, and then add them one by one.
13 Please implement currying for ternary functions
14 Please briefly describe the role of ref struct
15 Please briefly describe how to use the return return
16 Please use foreach and ref to add 1 to each element in an array
17 Please briefly describe the difference between ref , out and in when used as function parameter modifiers
18 Please briefly describe the IDisposable implementation method of non-sealed classes
19 What is the essence of delegate and event? Please briefly describe their implementation mechanism
Analysis:
11. Briefly describe the lazy computing mechanism of LINQ
Lazy computing refers to delay calculation, which may be reflected in the expression tree in the parsing stage and the state machine in the evaluation stage.
The first is the expression tree in the parsing stage. When the C# compiles, it will save these statements in the form of an expression tree. When evaluating, the C# compiler will translate all the expression trees into evaluation methods (such as executing SQL statements in the database).
The second is the state machine in the evaluation stage. LINQ to Objects can use an interface like IEnumemrable<T>, which does not necessarily save data itself. Only when evaluating, it returns an iterator - IEnumerator<T> , which will evaluate based on MoveNext() / Value.
These two mechanisms ensure that LINQ is calculated latently.
12. Use SelectMany to implement the elements in two arrays as Cartesian sets, and then add them one by one.
// 11. Use `SelectMany` to implement the addition of elements in two arraysint[] a1 = { 1, 2, 3, 4, 5 }; int[] a2 = { 5, 4, 3, 2, 1 }; a1 .SelectMany(v => a2, (v1, v2) => $"{v1}+{v2}={v1 + v2}") .Dump();
Analysis and explanation: Most people may only understand SelectMany's scenario (two-parameter overload, similar to flatMap), but it also provides this three-parameter overload, which allows you to do many-to-many-Cartes set. Therefore, these codes can actually be represented by the following LINQ:
from v1 in a1 from v2 in a2 select $"{v1}+{v2}={v1 + v2}"
The execution effect is exactly the same.
13. Please implement currying for ternary functions
Analysis: Currying refers to the process of converting f(x, y) to f(x)(y), the same as ternary and binary:
Func<int, int, int, int> op3 = (a, b, c) => (a - b) * c; Func<int, Func<int, Func<int, int>>> op11 = a => b => c => (a - b) * c; op3(4, 2, 3).Dump(); // 6 op11(4)(2)(3).Dump(); // 6
By implementing a generic method, implementing a general ternary function Currying:
Func<T1, Func<T2, Func<T3, TR>>> Currylize3<T1, T2, T3, TR>(Func<T1, T2, T3, TR> op) { return a => b => c => op(a, b, c); } // Test code:var op12 = Currylize3(op3); op12(4)(2)(3).Dump(); // (4-2)x3=6
Now I understand why you can not write parameters for F# signatures, because the parameters are really too long 😂
14. Please briefly describe the role of ref struct
ref struct is a new feature released in C# 7.2. It is mainly to cooperate with Span<T> to prevent Span<T> from being misused.
Why is it misused? Because Span<T> represents a continuous, fixed memory that can be accessed by managed and unmanaged code (no additional fixed required) These memory can be from stackalloc, can also obtain the managed location from fixed, and can also be directly allocated through () and other means. These memory should be fixed and cannot be moved by the managed heap. But the previous code didn't ensure this very well, so the ref struct was added to ensure it.
Based on the fact that it is not managed by a managed heap, we can summarize the following conclusions:
(1) Ref struct cannot be boxed (because binning becomes a reference type) - including the inability to convert to object and dynamic
(2) Any interface is prohibited (because the interface is a reference type)
(3) It is prohibited to use ref struct as a member or automatic attribute in class and struct (because it is prohibited to move at will, it cannot be placed in the managed heap. The reference type, struct member and automatic attribute may all be in managed memory)
(4) It is forbidden to use ref struct in iterator (yield) (because iterator is essentially a state machine, and the state machine is a reference type)
(5) Used in Lambda or local functions (because Lambda/local functions are closures, and the closure will generate a class with reference type)
There was often a question in the past. We often say that the value type is in the stack and the reference type is in the heap. Where does the value type member placed in the reference type exist in the memory? (In the heap, but must be copied to the stack for use)
Adding ref struct will never have this problem again.
15. Please briefly describe how to use the return return
This is also a similar problem. C# has always had value types. We often compare C++'s type system (only value types). It has inherent performance benefits, but C# was prone to unnecessary copying before - resulting in C# not enjoying the advantage of value types well.
Therefore, C# 7.0 introduced ref return, and then introduced ref parameter to be assigned in C# 7.3.
Example of usage:
Span<int> values = stackalloc int[10086]; values[42] = 10010; int v1 = SearchValue(values, 10010); v1 = 10086; (values[42]); // 10010 ref int v = ref SearchRefValue(values, 10010); v = 10086; (values[42]); // 10086; ref int SearchRefValue(Span<int> span, int value) { for (int i = 0; i < ; ++i) { if (span[i] == value) return ref span[i]; } return ref span[0]; } int SearchValue(Span<int> span, int value) { for (int i = 0; i < ; ++i) { if (span[i] == value) return span[i]; } return span[0]; }
Notes:
(1) The parameters can be Span<T> or ref T
(2) Use return return val when returning
(3) Note that the return value needs to be added with ref
(4) When assigning, the variables on both sides of the equal sign need to add the ref keyword ( ref int v1 = ref v2 )
In fact, this ref is the same as a pointer in C/C++.
16. Please use foreach and ref to add 1 to each element in an array
int[] arr = { 1, 2, 3, 4, 5}; ((",", arr)); // 1,2,3,4,5 foreach (ref int v in ()) { v++; } ((",", arr)); // 2,3,4,5,6
Note that foreach cannot be used with var, nor can it be used directly with int. Ref int is required. Note that arr needs to be converted to Span<T>.
17. Please briefly describe the differences between ref , out and in when used as function parameter modifiers
The ref parameter can be used for input or output at the same time (the variable must be initialized before use);
The out parameter is only used for output (no initialization is required before use);
The in parameter is only used for input, it is passed by reference, which ensures that it is not modified during use (the variable must be initialized before use);
You can use a table to compare their differences:
Modifier/Difference | ref | out | in | none |
Whether to copy | × | × | × | √ |
Can be modified | √ | √ | × | × |
enter | √ | × | √ | √ |
Output | √ | √ | × | × |
Need to be initialized | √ | × | √ | √ |
In fact, in is equivalent to const T& in C++. I hoped that C# would add this function many years ago.
18. Please briefly describe the IDisposable implementation method of non-sealed classes
There is only one method for normal IDisposable implementation:
void Dispose() { // free managed resources... // free unmanaged resources... }
But its disadvantage is that it must be called manually or using the using method. If you forget to call it, the system's garbage collector will not be cleaned, which will cause waste of resources. If it is called many times, there may be problems, so Dispose mode is required.
The Dispose pattern requires caring about C#'s finalizer function (some people call it a destructor, but I don't recommend it because it does not correspond to the constructor constructor), and its final version should look like this:
class BaseClass : IDisposable { private bool disposed = false; ~BaseClass() { Dispose(disposing: false); } protected virtual void Dispose(bool disposing) { if (disposed) return; if (disposing) { // free managed resources... } // free unmanaged resources... disposed = true; } public void Dispose() { Dispose(disposing: true); (this); } }
It has the following points to note:
(1) Introduce disposed variables to determine whether they have been recycled. If they have been recycled, they will not be recycled;
(2) Use protected virtual to ensure the correct recycling of subclasses, and be careful not to add them to the Dispose method;
(3) Use dispose to determine whether it is a .NET terminal recovery or a manual call to Dispose recovery. Terminator recovery no longer needs to care about releasing managed memory;
(4) Use (this) to avoid multiple calls to Dispose;
As for why this question requires concern for non-sealed classes, because sealed classes do not need to care about inheritance, so protected virtual may not be needed.
When subclasses inherit from this class and have more different resources to manage, the implementation method is as follows:
class DerivedClass : BaseClass { private bool disposed = false; protected override void Dispose(bool disposing) { if (disposed) return; if (disposing) { // free managed resources... } // free unmanaged resources... (disposing); } }
Notice:
(1) The inheritance class also needs to define a new and different distributed value, which cannot be shared with the old distributed;
(2) Other judgments and release orders are exactly the same as those of the base class;
(3) After the inheritance class is released, call (dispose) to release the parent class.
19. What is the essence of delegate and event? Please briefly describe their implementation mechanism
Delegate and event are essentially multicast delegate (MultipleDelegate), which wraps multiple Delegates in the form of an array. The Delegate class and function pointers in C are a bit similar, but they both retain the type and this, so they are both type-safe.
When delegate is defined, a type inherited from MultipleDelegate will be automatically created. Its constructor is ctor(object o, IntPtr f), the first parameter is this value, and the second parameter is a function pointer, which means that when delegate assignment, a subclass of MultipleDelegate is automatically created.
When delegate calls(), the compiler translates to .Invoke().
Note: The class created by delegate itself is also inherited from MultipleDelegate rather than Delegate, so it can also specify multiple responses like events:
string text = "Hello World"; Action v = () => (text); v += () => (); v(); // Hello World // 11
Note that the += operator will be translated as () by the compiler, and the -= operator will be translated as ().
Events are a special multicast delegate generated by the compiler. The default (customizable) code generated by the compiler. Compared with the MultipleDelegate generated by the delegate, events ensure the thread safety of the += and -= operators, and also ensure that they can be assigned values when null (just).
Summarize
These techniques may be unpopular in normal times, and being able to answer all of them correctly does not mean that they will be very useful, and it may be difficult to have the opportunity to use them.
But if you are developing ultra-high-performance network servers, middleware like Core, high-performance game engines like Unity 3D, or doing some high-performance real-time ETL, you can rely on this knowledge to make performance that is comparable to or even exceeds C/C++, and you can also enjoy the convenience of C#/.NET.
Some people in the group jokingly say that the companies that asked these questions during the interview were either too hearted or at least 60k, so this article was named 60k boss.
A moment of relaxation - My private .NET backend 6k interview questions:
1. How many bytes does the .NET int occupy?
2 What is the difference between the value type and the reference type of .NET? What are the differences in performance?
3 What is the data structure inside List<T>?
4 What is the data structure inside Dictionary<K, V>?
5. What is the difference between internal and protected?
6 What is the difference between string/StringBuilder?
7. Tell out the commonly used Http status codes and usage scenarios;
8. What are the tips for improving performance using the Entity Framework?
9 What is jwt (json web token) and what parts does it consist of?
10 How many bytes are required to calculate the DateTime type (requires calculation process)
The above is the detailed content of answering the 19 C# interview questions (Part 2) of the "60k" boss. For more information about C# interview questions, please pay attention to my other related articles!