Closed ufcpp closed 11 months ago
Inline Arrays
using System.Runtime.CompilerServices;
Buffer<int> b = default;
// Span への変換は暗黙。
Span<int> s = b;
// Length 取るのは Span 介さないと無理そう。
Console.WriteLine(s.Length);
// インデクサーはコンパイラー提供。
b[1] = 1;
// foreach もコンパイラー提供。
foreach (var x in b)
{
Console.WriteLine(x);
}
// 今、属性でサイズ指定する。
// Buffer<T, 10> とか書きたいけど、ジェネリック整数引数が .NET にはないんで。
// 利用側じゃなくて型定義側に属性つける。サイズごとに別の型作る必要あり。
[InlineArray(10)]
public struct Buffer<T>
{
private T _element0;
// ↑ 今、 private mamber unused サジェストが出っぱなしになったりする。
}
UnsafeAccessor
using System.Runtime.CompilerServices;
var p = new Private();
GetValue(p) = 99;
Console.WriteLine(p); // 99
// アクセス制限無視してメンバーにアクセスする手段を用意したらしい。
// 今まではリフレクション(IL Emit)でやりたい放題やってたけども、
// AOT (リフレクションをソースジェネレーターで置き換えたい)で困るからって。
//
// ランタイム側で UnsafeAccessor 属性付きの external メソッドを特別扱い(intrinsic)してる。
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_value")]
extern static ref int GetValue(Private x);
class Private
{
private int _value; // Make field readonly 出っぱなしにはなる。
public override string ToString() => _value.ToString();
}
ConfigureAwaitOptions
var cts = new CancellationTokenSource();
cts.Cancel();
try
{
await Task.Delay(100, cts.Token);
}
catch (OperationCanceledException)
{
// キャンセルで例外いちいち出さないでほしくて無条件 catch からの無視とかよくやる。
}
// だったらさ最初から throw しないでくれと。
await Task.Delay(100, cts.Token)
.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
// 他にもパフォーマンス制御用の enum 値がいくつか。
Interceptor 動くコード書けた。
using System.Runtime.CompilerServices;
var c = new C();
c.InterceptableMethod(1); // (L1,C1): prints "interceptor 1"
c.InterceptableMethod(1); // (L2,C2): prints "other interceptor 1"
c.InterceptableMethod(2); // (L3,C3): prints "other interceptor 2"
c.InterceptableMethod(1); // prints "interceptable 1"
class C
{
public void InterceptableMethod(int param)
{
Console.WriteLine($"interceptable {param}");
}
}
// generated code
static class D
{
[InterceptsLocation(@"[full path to Program]\Program.cs", 4, 3) ]
public static void InterceptorMethod(this C c, int param)
{
Console.WriteLine($"interceptor {param}");
}
[InterceptsLocation(@"[full path to Program]\Program.cs", 5, 3)]
[InterceptsLocation(@"[full path to Program]\Program.cs", 6, 3)]
public static void OtherInterceptorMethod(this C c, int param)
{
Console.WriteLine($"other interceptor {param}");
}
}
<Features>InterceptorsPreview</Features>
オプション必須。
属性は自前でも OK。
namespace System.Runtime.CompilerServices;
[Diagnostics.Conditional("CompileTimeOnly")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
internal sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber)
{
_ = path;
_ = lineNumber;
_ = columnNumber;
}
}
ドキュメントから属性のシグネチャとか、ファイル名がフルパスでないとダメとかの変更があり。 「intercept される側」には属性不要になったっぽい。
Feature オプションの指定が必要だったり、他の機能となんか実装者のノリの違いあり。 中の人曰く、「language feature じゃなくて compiler feature とする」とのこと。 (言語仕様であれば、「オプションで単機能の on/off とかやりたくない」ってずっと言ってる。NRT が例外中の例外。) (Obsolete 属性とか Conditional 属性とかも「言語仕様」というよりは「コンパイラーが属性を特別扱いする」なので、それと似たノリ。)
pathmap、コンパイラーは対応してるけど、IDE は対応してなさそう。
<ProjRoot>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))</ProjRoot>
<PathMap>$(ProjRoot)=.</PathMap>
[InterceptsLocation(@".\Program.cs", 4, 3) ]
public static void InterceptorMethod(this C c, int param)
{
Console.WriteLine($"interceptor {param}");
}
ビルドは通るけど、Visual Studio のエディター内では赤線出っぱなし。
F12 で intercept してる側に飛べない。
ref InlineArray ダメそう。
using System.Runtime.CompilerServices;
// size 見るに 80 なのでちゃんと InlineArray 効いてそう。
unsafe
{
Console.WriteLine(sizeof(S));
}
// new はできる。
var s = new S();
int x = 1;
// これがなんかダメっぽい。
// CS0021 Cannot apply indexing with [] to an expression of type 'S'
s[0] = ref x;
[InlineArray(10)]
ref struct S
{
ref int x;
}
using System.Runtime.CompilerServices;
int x = 1;
scoped RefInt r = new() { Ref = ref x };
// この場合エラー内容が変わった。
// CS0306 The type 'RefInt' may not be used as a type argument
// 内部的に Span<T> を使ってるから、ref struct を型引数にできない系のエラーっぽい。
scoped var s = new S();
s[0] = r;
[InlineArray(10)]
ref struct S
{
RefInt x;
}
ref struct RefInt
{
public ref int Ref;
}
interceptor、 file-local な型の拡張メソッドでも動く。
top-level statements 内に書いた local function に対しても働く。
m("abc");
int x = 1;
n();
static void m(string s) { }
void n() { x++; }
Console.WriteLine(x);
[InterceptsLocation(@"C:\src\ConsoleApp1\ConsoleApp1\Program.cs", 7, 1)]
internal static void M(string param)
{
Console.WriteLine($"top-level static local function {param}");
}
[InterceptsLocation(@"C:\src\ConsoleApp1\ConsoleApp1\Program.cs", 10, 1)]
internal static void M()
{
Console.WriteLine($"top-level non-static local function");
}
報告事案かも
using System.Runtime.CompilerServices;
var buffer = new Buffer();
// ユーザー定義 Enumerator 呼ばれないっぽい。
// 意図的?仕様漏れ?
foreach (var x in buffer)
{
Console.WriteLine(x);
}
[InlineArray(10)]
struct Buffer
{
private int _firstElement;
public Enumerator GetEnumerator() => default;
public struct Enumerator
{
public int Current => 0;
public bool MoveNext() => false;
}
}
using System.Runtime.CompilerServices;
var buffer = new Buffer();
int x = 1;
buffer[0] = ref x; // error on this line
// no error on the type.
[InlineArray(10)]
ref struct Buffer
{
private ref int _firstElement;
}
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
var buffer = new Buffer();
// ユーザー定義インデクサー呼ばれないっぽい。
// 意図的?仕様漏れ?
buffer[1] = 123;
foreach (var x in buffer)
{
Console.WriteLine(x);
}
[InlineArray(10)]
struct Buffer
{
private int _firstElement;
[UnscopedRef]
public ref int this[int index] => ref _firstElement;
}
拡張メソッドいけた。
static class Ex
{
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_value")]
public extern static ref int GetValue(this Private x);
}
// こいつに interceptor 挟んでくれればいいんじゃない?
Regex.Match("", "");
partial class C
{
// いちいち、1正規表現 1メソッド書くのやだーーー
[GeneratedRegex("")]
public static partial Regex M();
}
New C# 12 preview features:
Buffer x = new(); x[0] = ...;
みたいなのは書けるint[10] x;
みたいな構文の案もあったはずだけど、これは未実装.NET SDK の API diff (core 8617)見るに
IDE は拡張マネージャーの UI 新しくなったくらい?