Preface #
There is a thing called duck type, the so-called duck typing is that as long as things behave like a duck then be able to launch this thing is a duck.
C # is actually hidden inside a lot of similar types of ducks, but many developers do not know and, therefore, can not make use of these things, so today I'm hidden in the breakdown of what these compilers in detail.
Not only Task
and ValueTask
to await
#
When writing asynchronous code in C #, we often choose asynchronous code is contained in one Task
or ValueTask
, the caller will be able to use this await
way to achieve an asynchronous call.
Xika Xi, is not the only Task
and ValueTask
ability await
. Task
And ValueTask
behind the thread pool is obviously involved in scheduling, but why the C # async
/ await
was said to be coroutine
it?
Because you await
are not necessarily things Task
/ ValueTask
, in C # as long as your class contains GetAwaiter()
methods and bool IsCompleted
properties, and GetAwaiter()
things returned contains a GetResult()
method, a bool IsCompleted
property and implemented INotifyCompletion
, the object of this class is that you can await
of.
Therefore, in the package I / O operations, we can achieve a self Awaiter
, which is based on the underlying epoll
/ IOCP
implementation, so that when await
the time does not create any threads, also without any thread scheduling, but to directly Control. The OS after the completion of I / O by calling CompletionPort
(Windows), etc. to inform the user mode to complete an asynchronous call, then resume the context continue with 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 .NET Core I / O associated with asynchronous API is indeed doing so, the process I / O operation will not have any thread waiting for the results of the allocation are coroutine
operating: the direct I / O operations begin let out of control until the I / O operation is complete. The reason that sometimes you find await
before and after the thread has changed, it is only because Task
itself is scheduled.
UWP used in the development of IAsyncAction
/ IAsyncOperation<T>
is from the bottom of the package, and Task
has nothing to do but can await
, and if the C ++ / WinRT development UWP then return these interfaces are also possible methods co_await
of.
Not only IEnumerable
and IEnumerator
in order to be foreach
#
We will often write the following code:
foreach (var i in list)
{
// ......
}
Then one can ask why foreach
, because most of them will reply that list
implements IEnumerable
or IEnumerator
.
But in fact, if you want to be an object foreach
, only you need to provide a GetEnumerator()
method, and the GetEnumerator()
returned object contains a bool MoveNext()
add a method Current
attribute.
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
in order to be await foreach
#
As above, but this time the requirements have changed, GetEnumerator()
and MoveNext()
changed GetAsyncEnumerator()
and MoveNextAsync()
.
In which MoveNextAsync()
things should be a return Awaitable<bool>
, as this Awaitable
what in the end is that it can be Task
/ ValueTask
may be other or your own implementation.
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)
{
// ......
}
}
}
ref struct
How to achieve IDisposable
#
As we all know ref struct
because it is necessary and can not be boxed in on the stack, it can not implement the interface, but if you ref struct
have a void Dispose()
so it can be used using
to achieve the object syntax is automatically destroyed.
ref struct MyDisposable
{
public void Dispose() => throw new NotImplementedException();
}
class Program
{
public static void Main()
{
using var y = new MyDisposable();
// ......
}
}
Not only Range
can use slices #
C # 8 introduces Ranges, allowing the slicing operation, but in fact is not necessary to provide a receiving Range
indexer can type parameters to use this feature.
As long as your class can be counted (owned Length
or Count
attributes), and may be sliced (to have a Slice(int, int)
method), then 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 only Index
can use the index #
C # 8 Indexes for the introduction of the index, such as the use of ^1
the index penultimate element, but is actually not necessary to provide a receiving Index
indexer can type parameters to use this feature.
As long as your class can be counted (owned Length
or Count
attributes) and may be indexed (has a receiver int
indexer parameters), then you can use this feature.
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];
}
}
To achieve the type of deconstruction #
How to achieve a type of deconstruction of it? In fact, just write a name for Deconstruct()
the method, and the parameters are out
can be.
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 realized IEnumerable
in order to use LINQ
#
LINQ
C # is commonly used in an integrated query language allows you to write code like this:
from c in list where c.Id > 5 select c;
However, in the above code list
types do not necessarily have to realize IEnumerable
, in fact, as long as there is a corresponding name extension methods can be, for example, has called the Select
method can be used select
, with the known Where
methods can be used 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;
}
}