SoFunction
Updated on 2025-03-06

9 "Black Magic" in C#

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"):

  1. LINQ operation, with IEnumerable<T> type;
  2. async/await, with Task/ValueTask type;
  3. Expression tree, with Expression<T> type;
  4. Interpolate string, with FormattableString type;
  5. yield return, with IEnumerable<T> type;
  6. foreach loop, with IEnumerable<T> type;
  7. using keyword, interface with IDisposable;
  8. T?, with Nullable<T> type;
  9. Any type of Index/Range generic operation.

1. LINQOperation, withIEnumerable<T>type

It is not "black magic", it is the "duck type".

LINQyesC# 3.0New features released can easily operate data. Now12Years 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,LINQIt does not have to be based onIEnumerable<T>, just define a type to implement the requiredLINQJust expression,LINQofselectKeywords will be called.SelectMethods: 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/ValueTasktype

It is not "black magic", it is the "duck type".

async/awaitPosted onC# 5.0, can be very convenient to do asynchronous programming, and its essence is a state machine.

async/awaitThe essence is to find the type next name calledGetAwaiter()The interface must return an inherited fromINotifyCompletionorICriticalNotifyCompletionclass, this class needs to be implementedGetResult()Methods andIsCompleteproperty.

  • 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 visitGithubSpecific 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 &lt;= 4; ++i)
 {
  ($"The{i}Second-rate,time:{("HH:mm:ss")} - Thread number:{}");
  await (1000);
 }
 ("Execution Completed");
}

class TaskEx
{
 public static MyDelay Delay(int ms) =&gt; new MyDelay(ms);
}

class MyDelay : INotifyCompletion
{
 private readonly double _start;
 private readonly int _ms;
 
 public MyDelay(int ms)
 {
  _start = ;
  _ms = ms;
 }
 
 internal MyDelay GetAwaiter() =&gt; this;
 
 public void OnCompleted(Action continuation)
 {
  Tick += Check;
  
  void Check()
  {
   if ( - _start &gt; _ms)
   {
    continuation();
    Tick -= Check;
   }
  }
 }

 public void GetResult() {}
 
 public bool IsCompleted =&gt; 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 treeyesC# 3.0along withLINQReleasing 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, andFormattableStringtype

It is "black magic" and there is no "operation space".

Interpolate stringPosted 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 returnIn addition toIEnumerable<T>In addition, it can also be used forIEnumerableIEnumerator<T>IEnumerator

Therefore, if you want to useC#To simulateC++/Javaofgenerator<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. foreachloop, withIEnumerable<T>type

It is the "duck type" and has "operation space".

foreachIt 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 foreachAsynchronous 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 foreachyesC# 8.0along withAsynchronous flowThe details are released together, which can be seen in the "Code Demonstration of New Features of C# Versions" I wrote before.

7. usingKeywords, andIDisposableinterface

Yes, not.

Reference TypeAnd normalValue TypeuseusingKeywords must be based onIDisposableinterface.

butref structandIAsyncDisposableIt's another story, becauseref structIt is not allowed to move casually, but the reference type - hosted heap will allow memory to move, soref structNot allowed andReference TypeAny relationship that arises, this relationship includes inheritanceinterface--becauseinterfaceTooReference 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, ifIAsyncDisposableinterface:

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 acceptnullValue (normalValue TypeNot allowed to acceptnullvalue).

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/RangeGeneric Operations

There is "black magic" and "duck type" - there is room for operation.

Index/RangePosted onC# 8.0, can be likePythonThis facilitates the operation of index positions and the corresponding values. Need to call beforeSubstringComplex 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 callSubstringNow.

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 createdRangeType, then called<int>, completely belongs to "black magic".

But it is also a "duck" type, as long as it is implemented in the codeLengthProperties 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.0The "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!