23
24

.NET Framework でも C# の最新機能を使う

Posted at

確認環境

  • .NET 8

LangVersionlatest を指定すると最新のC#バージョンになる。デフォルトは TargetFramework で決まる。

  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

以下は、C# 7.3 より後に実装された機能が .NET Framework 4.7.2 で使用できるかの調査結果。

凡例

凡例 意味
対応
⚠️ 部分的に対応
未対応
影響なし

⛔ 使用できない機能一覧

  • 既定のインターフェイスメソッド
  • ジェネリック数値演算
  • ref フィールド
  • 警告ウェーブ7
  • インライン配列

C# 8

✅ Readonly member: 読み取り専用メンバー

struct Foo
{
    private int _value;
    public readonly int GetValue() => _value;
}

⛔ Default interface methods: 既定のインターフェイスメソッド

ランタイムレベルの対応が必要なので使用不可。

interface IFoo
{
    int GetValue() => 1;
}
error CS8701: ターゲット ランタイムは、既定のインターフェイスの実装をサポートしていません。

✅ Pattern matching enhancements: パターンマッチングの拡張

✅ switch式

var x = 1;
var r = x switch
{
    0 => '0',
    1 => '1',
    _ => '?',
};

✅ プロパティーパターン

var x = DateTime.Now;
var r = x is { Year: 2023, Month: 12 };

✅ 位置指定パターン

var x = (1, 2);
var r = x switch
{
    (0, 0) => 0,
    (1, 2) => 1,
    _ => -1,
};

✅ Using declarations: using 宣言

using var stream = new MemoryStream();

✅ Static local functions: 静的ローカル関数

static int GetValue() => 1;
GetValue();

✅ Disposable ref structs: 破棄可能な ref 構造体

using var foo = new Foo();

ref struct Foo
{
    public void Dispose() { }
}

⚠️ Nullable reference types: Null 許容参照型

BCLの支援はないが、ユーザーコード内であれば問題なく使用できる。

  <PropertyGroup>
    <Nullable>enable</Nullable>
  </PropertyGroup>
string? x = null;
namespace System.Diagnostics.CodeAnalysis
{
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
    public sealed class AllowNullAttribute : Attribute { }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
    public sealed class DisallowNullAttribute : Attribute { }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
    public sealed class MaybeNullAttribute : Attribute { }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
    public sealed class NotNullAttribute : Attribute { }

    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
    public sealed class MaybeNullWhenAttribute : Attribute
    {
        public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
        public bool ReturnValue { get; }
    }

    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
    public sealed class NotNullWhenAttribute : Attribute
    {
        public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
        public bool ReturnValue { get; }
    }

    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
    public sealed class NotNullIfNotNullAttribute : Attribute
    {
        public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
        public string ParameterName { get; }
    }

    [AttributeUsage(AttributeTargets.Method, Inherited = false)]
    public sealed class DoesNotReturnAttribute : Attribute { }

    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
    public sealed class DoesNotReturnIfAttribute : Attribute
    {
        public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
        public bool ParameterValue { get; }
    }

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
    public sealed class MemberNotNullAttribute : Attribute
    {
        public MemberNotNullAttribute(string member) => Members = new[] { member };
        public MemberNotNullAttribute(params string[] members) => Members = members;
        public string[] Members { get; }
    }

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
    public sealed class MemberNotNullWhenAttribute : Attribute
    {
        public MemberNotNullWhenAttribute(bool returnValue, string member)
        {
            ReturnValue = returnValue;
            Members = new[] { member };
        }

        public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
        {
            ReturnValue = returnValue;
            Members = members;
        }

        public bool ReturnValue { get; }
        public string[] Members { get; }
    }
}

✅ Asynchronous streams: 非同期ストリーム

await foreach (var value in GetValuesAsync())
{
    Console.WriteLine(value);
}

async IAsyncEnumerable<int> GetValuesAsync()
{
    for (var i = 0; i < 10; i++)
    {
        await Task.Delay(1000);
        yield return i;
    }
}
dotnet add package Microsoft.Bcl.AsyncInterfaces

⚠️ Indices and ranges: インデックスと範囲

公式のパッケージが存在しない。

var arr = new byte[10];
var sub = arr[1..2];

非公式のパッケージ。

dotnet add package IndexRange

または自分で定義。

namespace System
{
    public readonly struct Index : IEquatable<Index>
    {
        private readonly int _value;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Index(int value, bool fromEnd = false)
        {
            if (value < 0)
            {
                ThrowValueArgumentOutOfRange_NeedNonNegNumException();
            }

            if (fromEnd)
                _value = ~value;
            else
                _value = value;
        }

        private Index(int value)
        {
            _value = value;
        }

        public static Index Start => new Index(0);

        public static Index End => new Index(~0);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Index FromStart(int value)
        {
            if (value < 0)
            {
                ThrowValueArgumentOutOfRange_NeedNonNegNumException();
            }

            return new Index(value);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Index FromEnd(int value)
        {
            if (value < 0)
            {
                ThrowValueArgumentOutOfRange_NeedNonNegNumException();
            }

            return new Index(~value);
        }

        public int Value
        {
            get
            {
                if (_value < 0)
                    return ~_value;
                else
                    return _value;
            }
        }

        public bool IsFromEnd => _value < 0;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int GetOffset(int length)
        {
            int offset = _value;
            if (IsFromEnd)
            {
                offset += length + 1;
            }
            return offset;
        }

        public override bool Equals([NotNullWhen(true)] object? value) => value is Index && _value == ((Index)value)._value;

        public bool Equals(Index other) => _value == other._value;

        public override int GetHashCode() => _value;

        public static implicit operator Index(int value) => FromStart(value);

        public override string ToString()
        {
            if (IsFromEnd)
                return ToStringFromEnd();

            return ((uint)Value).ToString();
        }

        private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException()
        {
            throw new ArgumentOutOfRangeException("value", "value must be non-negative");
        }

        private string ToStringFromEnd()
        {
            return '^' + Value.ToString();
        }
    }

    public readonly struct Range : IEquatable<Range>
    {
        public Index Start { get; }

        public Index End { get; }

        public Range(Index start, Index end)
        {
            Start = start;
            End = end;
        }

        public override bool Equals([NotNullWhen(true)] object? value) =>
            value is Range r &&
            r.Start.Equals(Start) &&
            r.End.Equals(End);

        public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End);

        public override int GetHashCode()
        {
            return Start.GetHashCode() << 16 | End.GetHashCode();
        }

        public override string ToString()
        {
            return Start.ToString() + ".." + End.ToString();
        }

        public static Range StartAt(Index start) => new Range(start, Index.End);

        public static Range EndAt(Index end) => new Range(Index.Start, end);

        public static Range All => new Range(Index.Start, Index.End);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public (int Offset, int Length) GetOffsetAndLength(int length)
        {
            int start = Start.GetOffset(length);
            int end = End.GetOffset(length);

            if ((uint)end > (uint)length || (uint)start > (uint)end)
            {
                ThrowArgumentOutOfRangeException();
            }

            return (start, end - start);
        }

        private static void ThrowArgumentOutOfRangeException()
        {
            throw new ArgumentOutOfRangeException("length");
        }
    }
}

RuntimeHelpers.GetSubArray の定義。

namespace System.Runtime.CompilerServices
{
    public static partial class RuntimeHelpers
    {
        public static T[] GetSubArray<T>(T[] array, Range range)
        {
            if (array == null)
            {
                throw new ArgumentNullException(nameof(array));
            }

            (int offset, int length) = range.GetOffsetAndLength(array.Length);

            T[] dest;

            if (typeof(T[]) == array.GetType())
            {
                if (length == 0)
                {
                    return Array.Empty<T>();
                }

                dest = new T[length];
            }
            else
            {
                dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length);
            }

            Array.Copy(array, offset, dest, 0, length);

            return dest;
        }
    }
}

✅ Null-coalescing assignment: null 合体割り当て

string? v = null;
v ??= "";

✅ Unmanaged constructed types: 構築されたアンマネージド型

void Foo<T>() where T : unmanaged
{
}

✅ Stackalloc in nested expressions: 入れ子になった式の stackalloc

式の中で stackalloc が可能。ただしポインター T* ではなく Span<T> となる。

Foo(stackalloc int[1]);
void Foo(Span<int> p) { }
dotnet add package System.Memory

✅ Enhancement of interpolated verbatim strings: verbatim 補間文字列の拡張

var a = $@"";
var b = @$"";

C# 9

✅ Records: レコード

record C(int Value, string Name);

with 式で変更。

var c1 = new C(1, "v1");
var c2 = c1 with { Value = 2 };

✅ Init only setters: init 専用セッター

class C
{
    public int Value { get; init; }
}
namespace System.Runtime.CompilerServices
{
    public static class IsExternalInit { }
}

✅ Top-level statements: 最上位レベルのステートメント

{ }

✅ Pattern matching enhancements: パターンマッチングの拡張

relational patterns: リレーショナルパターン > < <= >=

var x = 10;
var r = x switch
{
    < 1 => "low",
    > 5 => "hi",
    _ => "ok",
};

logical patterns: 論理パターン and or not

var x = 10;
var r = x switch
{
    < 1 or > 5 => "outer",
    > 2 and < 4 => "center",
    _ => "inner",
};

int? n = null;
if (n is not null) { }

✅ Performance and interop: パフォーマンスと相互運用

✅ Native sized integers: ネイティブサイズの整数

nint x = 1;

⚠️ Function pointers: 関数ポインター

unsafe
{
    delegate* managed<int> f1;
    delegate* unmanaged[Cdecl]<int> f2;
}

delegate* unmanaged<int> のように呼び出し規約を省略することはできない。

✅ Suppress emitting localsinit flag: localsinit フラグの出力を抑制

[SkipLocalsInit]
unsafe void Foo()
{
    var v = stackalloc byte[100];
}
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Module
        | AttributeTargets.Class
        | AttributeTargets.Struct
        | AttributeTargets.Interface
        | AttributeTargets.Constructor
        | AttributeTargets.Method
        | AttributeTargets.Property
        | AttributeTargets.Event, Inherited = false)]
    public sealed class SkipLocalsInitAttribute : Attribute { }
}

✅ Module initializers: モジュール初期化子

static class C
{
    [ModuleInitializer]
    internal static void M()
    {
    }
}
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class ModuleInitializerAttribute : Attribute { }
}

✅ New features for partial methods: 部分メソッドの新機能

戻り値が void ではない部分メソッドをサポート。

partial class C
{
    public partial int GetValue();
}

partial class C
{
    public partial int GetValue() => 1;
}

✅ Fit and finish features: 適合性と完成度の機能

✅ Target-typed new expressions: ターゲットにより型指定された new 式

List<int> list = new();

✅ static anonymous functions: 静的匿名関数

Func<int> f = static () => 1;

✅ Target-typed conditional expressions: ターゲットにより型指定された条件式

A a = true ? new A1() : new A2();

class A { }
class A1 : A { }
class A2 : A { }

❔ Covariant return types: 共変の戻り値の型

この機能は実装されていない。

✅ Extension GetEnumerator support for foreach loops: foreachGetEnumerator 拡張メソッドに対応

foreach (var v in new C())
{
    Console.WriteLine(v);
}

class C
{
    public int V1 = 1;
    public int V2 = 2;
}

static class CExtensions
{
    public static CEnumerator GetEnumerator(this C c) => new(c);

    public struct CEnumerator : IEnumerator<int>
    {
        private readonly C c;
        private int index;

        public CEnumerator(C c)
        {
            this.c = c;
            this.index = 0;
        }

        public int Current => index == 1 ? this.c.V1 : index == 2 ? this.c.V2 : -1;

        object IEnumerator.Current => this.Current;

        public void Dispose() { }

        public bool MoveNext()
        {
            if (index is 0 or 1)
            {
                index++;
                return true;
            }

            return false;
        }

        public void Reset() => this.index = 0;
    }
}

✅ Lambda discard parameters: ラムダ式で破棄パラメーター

Action<int, string> f = (_, _) => { };

✅ Attributes on local functions: ローカル関数の属性

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Foo() {}

C# 10

✅ Record structs: レコード構造体

record struct S();
readonly record struct ROS();

✅ Improvements of structure types: 構造体型の機能強化

引数なしコンストラクタに対応

struct S
{
    public S() { }
}

with 式で構造体型と匿名型に対応

var v1 = new S();
var v2 = v1 with { Value = 2 };

record struct S(int Value, string Name);

✅ Interpolated string handlers: 補間文字列ハンドラー

Foo($"aaa{123}");

void Foo(Handler handler) { }

[InterpolatedStringHandler]
ref struct Handler
{
    public Handler(int literalLength, int formattedCount) { }

    public void AppendLiteral(string value) { }
    public void AppendFormatted<T>(T value) { }
}
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
    public sealed class InterpolatedStringHandlerAttribute : Attribute
    {
        public InterpolatedStringHandlerAttribute() { }
    }
}

✅ global using directives: global using ディレクティブ

global using Foo.Bar;

Implicit Usings

  <PropertyGroup>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <Using Remove="System.Net.Http" />
  </ItemGroup>

  <!-- or -->

  <ItemGroup>
    <PackageReference Include="System.Net.Http" Version="4.3.4" />
  </ItemGroup>

System.Net.Httpが存在しないため、UsingRemoveで除外するか、パッケージを参照必要がある。そうでない場合は下記のエラーとなる。

error CS0234: 型または名前空間の名前 'Http' が名前空間 'System.Net' に存在しません (アセンブリ参照があることを確認してください)

✅ File-scoped namespace declaration: ファイルスコープの名前空間宣言

namespace Foo.Bar;

✅ Extended property patterns: 拡張プロパティーパターン

入れ子のプロパティーパターンに対応

var r = x is { PropName1.PropName2: 2 };

✅ Improvements on lambda expressions: ラムダ式の機能強化

ラムダ式の自然型

var f = () => { }; // Action

ラムダ式の戻り値の型指定

var f = int () => 1;

ラムダ式の属性指定

var f3 = [return: NotNull] () => "";

✅ Allow const interpolated strings: 定数の補間文字列

const string S1 = "aaa";
const string S2 = "123";
const string S = $"{S1} {S2}";

✅ Record types can seal ToString(): レコード型で ToString() をシール

record C()
{
    public override sealed string ToString() => "aaa";
}

✅ Improved definite assignment: 限定代入の機能強化

null 状態分析が改善された。

✅ Allow both assignment and declaration in the same deconstruction: 同じ分解内の代入と宣言

var x = 0;
(x, int y) = (1, 2);

✅ Allow AsyncMethodBuilder attribute on methods: メソッドで AsyncMethodBuilder 属性を許可する

[AsyncMethodBuilder(typeof(TaskBuilder))]
async MyTask FooAsync() => await Task.Delay(0);

class TaskBuilder
{
    public static TaskBuilder Create() => new();
    public MyTask Task => new();
    public void SetException(Exception exception) { }
    public void SetResult() { }
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion
        where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    { }
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
          where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
          where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    { }
    public void Start<TStateMachine>(ref TStateMachine stateMachine)
          where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    { }
    public void SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine stateMachine) { }
}

class MyTask { }

✅ CallerArgumentExpression attribute: CallerArgumentExpression 属性

Foo(1.ToString() is { Length: 2 });

void Foo(bool condition, [CallerArgumentExpression(nameof(condition))] string? expression = null)
{
    if (!condition)
    {
        Console.WriteLine($"error: {expression}");
    }
}
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public sealed class CallerArgumentExpressionAttribute : Attribute
    {
        public CallerArgumentExpressionAttribute(string parameterName)
        {
            ParameterName = parameterName;
        }

        public string ParameterName { get; }
    }
}

✅ Enhanced #line pragma: 拡張 #line プラグマ

#line 100
Foo();
void Foo([CallerLineNumber] int lineNumber = 0) => Console.WriteLine(lineNumber);

C# 11

✅ Raw string literals: 未加工の文字リテラル

var s =
    """
    line1
    line2
    """;

⛔ Generic math support: ジェネリック数値演算サポート

静的抽象インターフェースメンバーに対応する必要がある。

var values = new int[] { 1, 2, 3 };
Console.WriteLine(Sum(values));

static T Sum<T>(IEnumerable<T> values)
    where T : INumber<T>
{
    T result = T.Zero;
    foreach (var value in values)
    {
        result += value;
    }
    return result;
}

✅ Generic attributes: ジェネリック属性

class G<T> : Attribute { }

[G<int>]
class C { }

✅ UTF-8 string literals: UTF-8 文字列リテラル

var data = "abc"u8;
dotnet add package System.Memory

✅ Newlines in string interpolation expressions: 文字列補間の改行

var s = $"aaa{
    123
}bbb";

✅ List patterns: リストパターン

var x = new int[] { 1, 2, 3, 4 };
var r = x is [ 1, 2, .. ];

✅ File-local types: ファイルローカル型

file class C { }

✅ Required members: 必須メンバー

var c1 = new C() { Value = 1 };
var c2 = new C(1);

class C
{
    public C() { }

    [SetsRequiredMembers]
    public C(int value)
    {
        this.Value = value;
    }

    public required int Value { get; set; }
}
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class RequiredMemberAttribute : Attribute { }
}

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
    public sealed class CompilerFeatureRequiredAttribute : Attribute
    {
        public CompilerFeatureRequiredAttribute(string featureName)
        {
            FeatureName = featureName;
        }
        public string FeatureName { get; }
        public bool IsOptional { get; init; }
        public const string RefStructs = nameof(RefStructs);
        public const string RequiredMembers = nameof(RequiredMembers);
    }
}

namespace System.Diagnostics.CodeAnalysis
{
    [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
    public sealed class SetsRequiredMembersAttribute : Attribute { }
}

✅ Auto-default structs: 自動デフォルト構造体

struct S
{
    int value;

    public S() { }
}

✅ Pattern match Span on a constant string: 文字列定数で Span<char> とパターンマッチ

var s = "foo".AsSpan();
var r = s is "foo";

✅ Extended nameof scope: 拡張 nameof スコープ

[return: NotNullIfNotNull(nameof(test))]
string? Foo(string? test) => test is not null ? "not null" : null;

❔ Numeric IntPtr: 数値 IntPtr

.NET 7 以降のランタイムの場合、nintnuint の特別扱いをしなくなる。(ジェネリック数値演算の関連)

⛔ ref fields: ref フィールド

ref struct S
{
    ref int value;
}

✅ scoped ref: scoped ref 変数

static Span<byte> Foo(scoped Span<byte> v) => new byte[1];
static Span<byte> Bar(Span<byte> v) => Foo(stackalloc byte[1]);

✅ Improved method group conversion to delegate: メソッドグループからデリゲートへの変換の改善

デリゲートにメソッドを渡すと毎回 new されていたが、キャッシュされるようになった。

Foo(A);
Foo(() => { });
static void Foo(Action a) => a();
static void A() { }

⛔ Warning wave 7: 警告ウェーブ7

小文字のみの型名を警告するコンパイラ警告の追加。

    <AnalysisLevel>7</AnalysisLevel>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
class foo { }

net472 にすると警告が抑制されるようだ。

C# 12

✅ Primary constructors: プライマリコンストラクター

class C(int value)
{
    int Value { get; } = value;
}

✅ Collection expressions: コレクション式

int[] values = [ 1, 2, 3 ];

✅ ref readonly parameters: ref readonly パラメーター

void Foo(ref readonly int value) {}

✅ Default lambda parameters: ラムダ式パラメーターの既定値

var f = (int value = 1) => { };

✅ Alias any type: 任意の型のエイリアス

using Alias = (int x, int y);
var value = new Alias(1, 2);

⛔ Inline arrays: インライン配列

ランタイムレベルの対応が必要。

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
    private int _element0;
}
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)]
    public sealed class InlineArrayAttribute : Attribute
    {
        public InlineArrayAttribute(int length)
        {
            Length = length;
        }
        public int Length { get; }
    }
}
error CS9171: ターゲットのランタイムはインライン配列型をサポートしていません。

✅ Experimental attribute: 試験段階の属性

[Experimental("Foo")]
void Foo() { }
namespace System.Diagnostics.CodeAnalysis
{
    [AttributeUsage(AttributeTargets.Assembly |
                    AttributeTargets.Module |
                    AttributeTargets.Class |
                    AttributeTargets.Struct |
                    AttributeTargets.Enum |
                    AttributeTargets.Constructor |
                    AttributeTargets.Method |
                    AttributeTargets.Property |
                    AttributeTargets.Field |
                    AttributeTargets.Event |
                    AttributeTargets.Interface |
                    AttributeTargets.Delegate, Inherited = false)]
    public sealed class ExperimentalAttribute : Attribute
    {
        public ExperimentalAttribute(string diagnosticId)
        {
            DiagnosticId = diagnosticId;
        }
        public string DiagnosticId { get; }
        public string? UrlFormat { get; set; }
    }
}

✅ Interceptors: インターセプター

new C().Foo();

class C
{
    public void Foo() => Console.WriteLine("Foo");
}

namespace MyInterceptors
{
    static class D
    {
        [InterceptsLocation(@"<FULL-PATH>\Program.cs", 9, 9)]
        public static void Bar(this C instance) => Console.WriteLine("Bar");
    }
}
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
#pragma warning disable CS9113
    public sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute
#pragma warning restore CS9113
    {
    }
}
  <PropertyGroup>
    <InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);MyInterceptors</InterceptorsPreviewNamespaces>
  </PropertyGroup>
23
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
24