⚛️

スレッドセーフを保証するクラス

に公開

Swift の actor[1]defer[2] を C# で再現してみました。

Atom<T>

C# 版の actor は DSL っぽい構文です。コレは Swift っぽくしたかったというよりは、スレッドセーフを強制する為に仕方なくという感じ。

var atom = new Atom<int>();

atom.Set = 42;
atom.Update = v => ++v;  // 43

atom.Set = 310;
atom.Read = v => Console.WriteLine(v);

読み取りのスレッドセーフをシンプルな構文で強制するのが難しい。

👇 .NET 7+ なら関数型言語っぽい書き方も出来る。

atom <<= 310;
atom >>= Console.WriteLine;

コイツらとは別に C# の標準的なスタイルで書けるバージョンもある。コッチは主にクロージャのアロケーション回避が目的。

atom.WriteLock((x: 42, y: "Tuple"), static (args, current) =>
{
    return current + args.x + args.y.Length;
});

参照型は ReadLock スコープ内で編集できちゃう。しょうがないね。

var atom = new Atom<MyClass>(value: new());

atom.ReadLock(foo, async static (foo, myClass) =>
{
    // 👇 ブロック全体がロック内なので1秒間他のスレッドはアクセス不能になる
    await Task.Delay(1000);

    myClass.Data = foo.Value;
});

// 👇 アロケを避けつつロック時間を最小化(適切ではない場合もある)
atom.ReadLock(foo, (foo, value) => foo.Data = value);
async foo.ProcessDataAsync();

ソースコード

https://github.com/sator-imaging/Unity-Fundamentals/blob/310d83180e722965011388bf47d77829b7e79219/Runtime/FancyStuff/Atom.cs

 

defer

C# 実装が既にある。言語レベルでのサポートじゃないので正直微妙。Go にもある機能だし書けない C# がおかしいレベル。

// ブロックを抜ける時にストリームを巻き戻す
using var _ = stream.Defer(stream => stream.Position = 0);

ソースコード

https://github.com/sator-imaging/Unity-Fundamentals/blob/main/Runtime/System/Defer.cs

 

guard

C# に一番欲しくて一番難しい奴。最近 ?? throw の代わりにヘルパーを使う方法が分かったけど return はどう逆立ちしても不可能。

[DoesNotReturn] static T Throw<T>() => throw new Exception();

int? value = null;
var x = value ?? Throw<int>();

 

おわりに

⬆⬆⬆ https://github.com/dotnet/csharplang/discussions/9695 ⬆⬆⬆

以上です。お疲れ様でした。

参考

脚注
  1. https://zenn.dev/sator_imaging/articles/a5d419867ff981#2.-actor-%3D-rust-が欲しかったやつ ↩︎

  2. https://zenn.dev/sator_imaging/articles/a5d419867ff981#2.-defer(raii-を構文にしたもの) ↩︎

Discussion