Not only Task and ValueTask can
await#
When writing asynchronous code in C#, we often choose to include the asynchronous code in a Task or ValueTask, so that the caller can use await
To implement asynchronous calls.
Sikassi, not only task and ValueTask can await. Task and ValueTask
The thread pool is clearly involved in scheduling, but why is async/await of C# called coroutine?
Because what you are await is not necessarily a Task/ValueTask, in C# as long as your class contains GetAwaiter() method and
bool IsCompleted property, and the things returned by GetAwaiter() include a GetResult() method and a ` bool
If the IsCompleted attribute and INotifyCompletion are implemented, the object of this class can be await '.
Therefore, when encapsulating I/O operations, we can implement an await er by ourselves, which is based on the underlying epoll/IOCP implementation
No thread will be created and no thread scheduling will occur, but control will be directly ceded. The OS uses CompletionPort after I/O calls are completed
(Windows) notifies the user state to complete the asynchronous call. At this time, the recovery context continues to execute the remaining logic, which is actually a real stackless coroutine.
public class MyTask<T>
{
public MyAwaiter<T> GetAwaiter() {
return new MyAwaiter<T>();
}
}
public class MyAwaiter<T> : INotifyCompletion
{
public bool IsCompleted { get; private set; }
public T GetResult() {
throw new NotImplementedException();
}
public void OnCompleted(Action continuation) {
throw new NotImplementedException();
}
}
public class Program
{
static async Task Main(string[] args) {
var obj = new MyTask<int>();
await obj;
}
}
In fact, the asynchronous API s related to I/O in the. NET Core do so. There will be no thread allocation waiting for results during I/O operations
coroutine operation: after the I/O operation starts, directly give up control until the I/O operation is completed. And sometimes you find that the threads change before and after await, just because
The Task itself is scheduled.
Iasyncaction / iasyncoperation < T > used in UWP development comes from the underlying encapsulation, which has nothing to do with tasks, but can be used
await, and if UWP is developed with C++/WinRT, the methods that return these interfaces can also be used_ await.
Not only IEnumerable and IEnumerator can be used
foreach#
We often write the following code:
foreach (var i in list)
{
// ......
}
When asked why foreach is available, most of them will reply because the list implements IEnumerable or IEnumerator.
But in fact, if you want an object to be foreach, you only need to provide a GetEnumerator() method and GetEnumerator()
The returned object contains a bool MoveNext() method plus a Current property.
class MyEnumerator<T>
{
public T Current { get; private set; }
public bool MoveNext() {
throw new NotImplementedException();
}
}
class MyEnumerable<T>
{
public MyEnumerator<T> GetEnumerator() {
throw new NotImplementedException();
}
}
class Program
{
public static void Main() {
var x = new MyEnumerable<int>();
foreach (var i in x)
{
// ......
}
}
}
Not only IAsyncEnumerable and IAsyncEnumerator can be ` await '
foreach`#
The same as above, but this time the requirements change. GetEnumerator() and MoveNext() become GetAsyncEnumerator() and MoveNext()
MoveNextAsync().
Among them, the thing returned by MoveNextAsync() should be an Awaitable < bool >. As for what the Awaitable is, it can be
Task/ValueTask can also be other or implemented by yourself.
class MyAsyncEnumerator<T>
{
public T Current { get; private set; }
public MyTask<bool> MoveNextAsync() {
throw new NotImplementedException();
}
}
class MyAsyncEnumerable<T>
{
public MyAsyncEnumerator<T> GetAsyncEnumerator() {
throw new NotImplementedException();
}
}
class Program
{
public static async Task Main() {
var x = new MyAsyncEnumerable<int>();
await foreach (var i in x)
{
// ......
}
}
}
How to implement ref struct
IDisposable#
As we all know, ref struct cannot implement the interface because it must be on the stack and cannot be boxed. However, if you have a 'void' in your ref struct
Dispose(), then you can use the using ` syntax to realize the automatic destruction of objects.
<pre style="margin: 10px 0px; padding: 0px; white-space: pre !important;
overflow-wrap: break-word; position: relative !important;">
ref struct MyDisposable
{
public void Dispose() => throw new NotImplementedException();
}
class Program
{
public static void Main() {
using var y = new MyDisposable();
// ......
}
}
Not just Range
To use slices #
C# 8 introduces Ranges to allow slicing, but it does not have to provide an indexer that receives Range type parameters to use this feature.
As long as your class can be counted (with the Length or Count attribute) and sliced (with a Slice(int, int)
Method), you can use this feature.
class MyRange
{
public int Count { get; private set; }
public object Slice(int x, int y) => throw new NotImplementedException();
}
class Program
{
public static void Main() {
var x = new MyRange();
var y = x[1..];
}
}
Not just Index
To use the index #
C# 8 introduces Indexes for indexing. For example, use ^ 1 to Index the penultimate element, but it is not necessary to provide a parameter that receives an Index type parameter
indexer can use this feature.
You can use this feature as long as your class can be counted (with the Length or Count attribute) and indexed (with an indexer that receives an int parameter).
class MyIndex
{
public int Count { get; private set; }
public object this[int index]
{
get => throw new NotImplementedException();
}
}
class Program
{
public static void Main() {
var x = new MyIndex();
var y = x[^1];
}
}
Deconstruct a type #
How to deconstruct a type? In fact, you only need to write a method named Deconstruct() and the parameters are out.
class MyDeconstruct
{
private int A => 1;
private int B => 2;
public void Deconstruct(out int a, out int b) {
a = A;
b = B;
}
}
class Program
{
public static void Main() {
var x = new MyDeconstruct();
var (o, u) = x;
}
}
Not only the implementation of IEnumerable can be used
LINQ#
LINQ is an integrated query language commonly used in C# and allows you to write code as follows:
from c in list where c.Id > 5 select c;
However, the list type in the above code does not necessarily have to implement IEnumerable. In fact, as long as there is an extension method with the corresponding name, such as Select
The method can use select. With the method called where, you can use where.
class Just<T> : Maybe<T>
{
private readonly T value;
public Just(T value) { this.value = value; }
public override Maybe<U> Select<U>(Func<T, Maybe<U>> f) => f(value);
public override string ToString() => $"Just {value}";
}
class Nothing<T> : Maybe<T>
{
public override Maybe<U> Select<U>(Func<T, Maybe<U>> _) => new Nothing<U>();
public override string ToString() => "Nothing";
}
abstract class Maybe<T>
{
public abstract Maybe<U> Select<U>(Func<T, Maybe<U>> f);
public Maybe<V> SelectMany<U, V>(Func<T, Maybe<U>> k, Func<T, U, V> s)
=> Select(x => k(x).Select(y => new Just<V>(s(x, y))));
public Maybe<U> Where(Func<Maybe<T>, bool> f) => f(this) ? this : new Nothing<T>();
}
class Program
{
public static void Main() {
var x = new Just<int>(3);
var y = new Just<int>(7);
var z = new Nothing<int>();
var u = from x0 in x from y0 in y select x0 + y0;
var v = from x0 in x from z0 in z select x0 + z0;
var just = from c in x where true select c;
var nothing = from c in x where false select c;
}
}