We knowC#
It is a very advanced language because it is its visionary "grammatical sugar". These "grammatical sugar" are sometimes too useful, leading to some people thinking it isC#
There is no reason to write dead things from the compiler - it's a bit like "black magic".
Then we can take a lookC#
These high-level language features are things that the compiler writes dead ("black magic") or "duck type" that can be extended (slutty operations).
I will list a directory first. You can try to judge this directory, and tell me whether it is "black magic" (written by the compiler) or "duck type" (you can customize "slutty operations"):
- LINQ operation, with IEnumerable<T> type;
- async/await, with Task/ValueTask type;
- Expression tree, with Expression<T> type;
- Interpolate string, with FormattableString type;
- yield return, with IEnumerable<T> type;
- foreach loop, with IEnumerable<T> type;
- using keyword, interface with IDisposable;
- T?, with Nullable<T> type;
- Any type of Index/Range generic operation.
1. LINQ
Operation, withIEnumerable<T>
type
It is not "black magic", it is the "duck type".
LINQ
yesC# 3.0
New features released can easily operate data. Now12
Years have passed, and although some functions need to be enhanced, they are still much more convenient than other languages.
As mentioned in my previous blog,LINQ
It does not have to be based onIEnumerable<T>
, just define a type to implement the requiredLINQ
Just expression,LINQ
ofselect
Keywords will be called.Select
Methods: Use the following "savory operation" to achieve the effect of "transplanting flowers and connecting wood":
void Main() { var query = from i in new F() select 3; ((",", query)); // 0,1,2,3,4 } class F { public IEnumerable<int> Select<R>(Func<int, R> t) { for (var i = 0; i < 5; ++i) { yield return i; } } }
2. async/await
,andTask
/ValueTask
type
It is not "black magic", it is the "duck type".
async/await
Posted onC# 5.0
, can be very convenient to do asynchronous programming, and its essence is a state machine.
async/await
The essence is to find the type next name calledGetAwaiter()
The interface must return an inherited fromINotifyCompletion
orICriticalNotifyCompletion
class, this class needs to be implementedGetResult()
Methods andIsComplete
property.
- First call the() method to get the waiter a;
- Call to obtain boolean type b;
- If b=true, execute() immediately to obtain the run result;
- If b=false, it depends on the situation:
If a does not implement ICriticalNotifyCompletion, then execute (a as INotifyCompletion).OnCompleted(action)
If a implements ICriticalNotifyCompletion, then (a as ICriticalNotifyCompletion).OnCompleted(action)
Execution is then paused, and OnCompleted returns to the state machine after completion;
If you are interested, please visitGithub
Specific specifications:/dotnet/csharplang/blob/master/spec/
normal()
It is based onThread pool timer
, you can use the following "spicy operation" to implement a single thread()
:
static Action Tick = null; void Main() { Start(); while (true) { if (Tick != null) Tick(); (1); } } async void Start() { ("Execution starts"); for (int i = 1; i <= 4; ++i) { ($"The{i}Second-rate,time:{("HH:mm:ss")} - Thread number:{}"); await (1000); } ("Execution Completed"); } class TaskEx { public static MyDelay Delay(int ms) => new MyDelay(ms); } class MyDelay : INotifyCompletion { private readonly double _start; private readonly int _ms; public MyDelay(int ms) { _start = ; _ms = ms; } internal MyDelay GetAwaiter() => this; public void OnCompleted(Action continuation) { Tick += Check; void Check() { if ( - _start > _ms) { continuation(); Tick -= Check; } } } public void GetResult() {} public bool IsCompleted => false; }
The operation effect is as follows:
Execution starts
The first time, time: 17:38:03 - Thread number: 1
The second time, time: 17:38:04 - Thread number: 1
The third time, time: 17:38:05 - Thread number: 1
The 4th time, time: 17:38:06 - Thread number: 1
Execution completed
Be careful not to use itTaskCompletionSource<T>
Only by creating a definedasync/await
。
3. Expression tree, withExpression<T>
type
It is "black magic", there is no "operation space", only if the type isExpression<T>
It will only be created as an expression tree.
Expression tree
yesC# 3.0
along withLINQ
Releasing together is a visionary "black magic".
As in the following code:
Expression<Func<int>> g3 = () => 3;
It will be translated by the compiler as:
Expression<Func<int>> g3 = <Func<int>>( (3, typeof(int)), <ParameterExpression>());
4. Interpolate strings, andFormattableString
type
It is "black magic" and there is no "operation space".
Interpolate string
Posted onC# 6.0
, many languages have provided similar features before this.
Only if the type isFormattableString
, will produce different compilation results, such as the following code:
FormattableString x1 = $"Hello {42}"; string x2 = $"Hello {42}";
The compiler generates the following results:
FormattableString x1 = ("Hello {0}", 42); string x2 = ("Hello {0}", 42);
Note that its essence is calledto create a type.
5. yield return
,andIEnumerable<T>
type;
It is "black magic", but there are additional instructions.
yield return
In addition toIEnumerable<T>
In addition, it can also be used forIEnumerable
、IEnumerator<T>
、IEnumerator
。
Therefore, if you want to useC#
To simulateC++
/Java
ofgenerator<T>
The behavior will be simpler:
var seq = GetNumbers(); (); (); // 0 (); (); // 1 (); (); // 2 (); (); // 3 (); (); // 4 IEnumerator<int> GetNumbers() { for (var i = 0; i < 5; ++i) yield return i; }
yield return
——"Iterator" is published onC# 2.0
。
6. foreach
loop, withIEnumerable<T>
type
It is the "duck type" and has "operation space".
foreach
It doesn't have to be used togetherIEnumerable<T>
Type, as long as the object existsGetEnumerator()
Method:
void Main() { foreach (var i in new F()) { (i + ", "); // 1, 2, 3, 4, 5, } } class F { public IEnumerator<int> GetEnumerator() { for (var i = 0; i < 5; ++i) { yield return i; } } }
Also, if the object is implementedGetAsyncEnumerator()
, even use the sameawait foreach
Asynchronous loop:
async Task Main() { await foreach (var i in new F()) { (i + ", "); // 1, 2, 3, 4, 5, } } class F { public async IAsyncEnumerator<int> GetAsyncEnumerator() { for (var i = 0; i < 5; ++i) { await (1); yield return i; } } }
await foreach
yesC# 8.0
along withAsynchronous flow
The details are released together, which can be seen in the "Code Demonstration of New Features of C# Versions" I wrote before.
7. using
Keywords, andIDisposable
interface
Yes, not.
Reference Type
And normalValue Type
useusing
Keywords must be based onIDisposable
interface.
butref struct
andIAsyncDisposable
It's another story, becauseref struct
It is not allowed to move casually, but the reference type - hosted heap will allow memory to move, soref struct
Not allowed andReference Type
Any relationship that arises, this relationship includes inheritanceinterface
--becauseinterface
TooReference Type
。
But the need to release resources still exists. What should I do? The "duck type" is here. You can write a handwritten one.Dispose()
Methods, no need to inherit any interface:
void S1Demo() { using S1 s1 = new S1(); } ref struct S1 { public void Dispose() { ("Normal release"); } }
The same is true, ifIAsyncDisposable
interface:
async Task S2Demo() { await using S2 s2 = new S2(); } struct S2 : IAsyncDisposable { public async ValueTask DisposeAsync() { await (1); ("Async release"); } }
8. T?
,andNullable<T>
type
It's "black magic", onlyNullable<T>
Only accept itT?
,Nullable<T>
As aValue Type
, it can also acceptnull
Value (normalValue Type
Not allowed to acceptnull
value).
The sample code is as follows:
int? t1 = null; Nullable<int> t2 = null; int t3 = null; // Error CS0037: Cannot convert null to 'int' because it is a non-nullable value type
The generated code is as follows (int?
andNullable<int>
Exactly the same, skipping the code that failed to compile):
IL_0000: nop IL_0001: 0 IL_0003: initobj valuetype []`1<int32> IL_0009: 1 IL_000b: initobj valuetype []`1<int32> IL_0011: ret
9. Any type ofIndex/Range
Generic Operations
There is "black magic" and "duck type" - there is room for operation.
Index/Range
Posted onC# 8.0
, can be likePython
This facilitates the operation of index positions and the corresponding values. Need to call beforeSubstring
Complex operations are very simple now.
string url = "/product/7705a33a-4d2c-455d-a42c-c95e6ac8ee99/summary"; string productId = url[35..("/")]; (productId);
The generated code is as follows:
string url = "/product/7705a33a-4d2c-455d-a42c-c95e6ac8ee99/amd-r7-3800x"; int num = 35; int length = ("/") - num; string productId = (num, length); (productId); // 7705a33a-4d2c-455d-a42c-c95e6ac8ee99
visible,C#
The compiler ignores itIndex/Range
, translated directly into a callSubstring
Now.
But the array is different:
var range = new[] { 1, 2, 3, 4, 5 }[1..3]; ((", ", range)); // 2, 3
The generated code is as follows:
int[] range = <int>(new int[5] { 1, 2, 3, 4, 5 }, new Range(1, 3)); (<int>(", ", range));
It can be seen that it has indeed been createdRange
Type, then called<int>
, completely belongs to "black magic".
But it is also a "duck" type, as long as it is implemented in the codeLength
Properties andSlice(int, int)
Methods can be calledIndex/Range
:
var range2 = new F()[2..]; (range2); // 2 -> -2 class F { public int Length { get; set; } public IEnumerable<int> Slice(int start, int end) { yield return start; yield return end; } }
The generated code is as follows:
F f = new F(); int length2 = ; length = 2; num = length2 - length; string range2 = (length, num); (range2);
Summarize
As seen above,C#
There are indeed many "black magic", but there are also many "duck types", and there are a lot of "operation space" for "sexy operation".
It is rumoredC# 9.0
The "duck type" will be added -Type Classes
, by then the "operation space" will definitely be larger than now, I am looking forward to it!
This is the end of this article about 9 "black magic" in C#. For more related content on C#, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!