rksoftware

Visual Studio とか C# とかが好きです

WPF で OpenFolderDialog

皆さん .NET 8 使っていますか? 使っていますよね? 知っています。皆さん使っています。

■ .NET 8 の WPF の新機能 OpenFolderDialog

learn.microsoft.com

learn.microsoft.com

.NET 8 で WPF で OpenFolderDialog が使えるそうです。
皆さん WPF で OpenFolderDialog 使いたかったですよね? 私は使いたかったです。何度自作したか......。

■ 試してみます。

試してみます。

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Content="Button" Click="Button_Click"/>
    </Grid>
</Window>
using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Microsoft.Win32.OpenFolderDialog ofd = new Microsoft.Win32.OpenFolderDialog {InitialDirectory= @"C:\sample", FolderName = "cs12" };
            MessageBox.Show(ofd.ShowDialog() == true ? ofd.FolderName : "キャンセルされました.");
        }
    }
}

実行してみます。

でました!

■ 暗黒の儀式

かつてはこんな闇の技術もありましたが、完全に闇に飲みさせることができるようになりますね!
「フォルダーの参照」より「フォルダーの選択」の方が使いやすいですし!

using System.Windows;

namespace WpfApp3
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog { SelectedPath = @"C:\sample\cs12" };
            MessageBox.Show(fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK ? fbd.SelectedPath : "キャンセルされました.");

        }
    }
}

WPF で Forms の dll 禁止のプロフジェクトに、このコードが書かれたソースコードが混入してします事件ともさらば!

RDP での WPF ハードウェア アクセラレーション

皆さん .NET 8 RC1 を使い始めていると思います。私も使い始めています。

そんな .NET 8 RC1 に気になる項目があります。「 RDP での WPF ハードウェア アクセラレーション 」です。
devblogs.microsoft.com これは、リモートデスクトップでアプリを使っているときに ハードウェア アクセラレーション が有効になるという素敵機能です。Virtual Desktop やリモートワークなどで活用の機会もおおいのではないでしょうか?
見てみましょう。

■ *.runtimeconfig.json

記事を読んでみると、*.runtimeconfig.json ファイルに次の要素があれば良さそうです。

 "configProperties": {
      "Switch.System.Windows.Media.EnableHardwareAccelerationInRdp": true
    } 

*.runtimeconfig.json はビルド結果のファイルにあります。こんな感じ。

{
  "runtimeOptions": {
    "tfm": "net8.0",
    "frameworks": [
      {
        "name": "Microsoft.NETCore.App",
        "version": "8.0.0-rc.1.23419.4"
      },
      {
        "name": "Microsoft.WindowsDesktop.App",
        "version": "8.0.0-rc.1.23420.5"
      }
    ],
    "configProperties": {
      "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true
    }
  }
}

要素を追加するための方法は 2 種類ある様子。一つ目は *.csproj に要素を追加する方法、二つ目は runtimeconfig.template.json ファイルに要素を追加する方法。
やってみましょう。

■ Visual Studio で WPF アプリを作る

今更気にすることもないことですが、Visual Studio で WPF アプリを作るとき、テンプレートが二つあるので間違えないようにしてください。

選ぶのは 「 WPF アプリケーション 」です。もう一つを選ぶと、この令和の時代に .NET Framework になってしまいます。

今回は 3 つのアプリを作って試してみます。

*.csproj に要素を追加するパターン

<ItemGroup>
      <RuntimeHostConfigurationOption Include="Switch.System.Windows.Media.EnableHardwareAccelerationInRdp" Value="true" />
</ItemGroup>

という要素を追加するとのことです。
追加する場所は <Project Sdk="Microsoft.NET.Sdk"> の配下で、こんな感じになります。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <RuntimeHostConfigurationOption Include="Switch.System.Windows.Media.EnableHardwareAccelerationInRdp" Value="true" />
  </ItemGroup>

</Project>

誤った場所に追加するとプロジェクトが開けなくなります。例えばこんな風に間違えると

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
    <ItemGroup>
      <RuntimeHostConfigurationOption Include="Switch.System.Windows.Media.EnableHardwareAccelerationInRdp" Value="true" />
    </ItemGroup>
  </PropertyGroup>

</Project>


こんな感じです。

*.csproj に要素を追加するパターン

プロジェクトのフォルダのトップ ( *.csproj がある場所) に runtimeconfig.template.json ファイルを作ります。

{
    "configProperties": {
    "Switch.System.Windows.Media.EnableHardwareAccelerationInRdp": true
  } 
}

ここに書いた要素が、ビルドの出力フォルダーの先述の何もしていない *.runtimeconfig.json と合成されます。

■ 3 つのアプリ

ということで、

  • 何もしていないアプリ
  • *.csproj に要素を追加したアプリ
  • runtimeconfig.template.json を追加したアプリ

の 3 つができました。こんな感じです。

■ 3 つの *.runtimeconfig.json ファイル

それぞれの *.runtimeconfig.json ファイルは次のようになりました。

何もしていないアプリ

{
  "runtimeOptions": {
    "tfm": "net8.0",
    "frameworks": [
      {
        "name": "Microsoft.NETCore.App",
        "version": "8.0.0-rc.1.23419.4"
      },
      {
        "name": "Microsoft.WindowsDesktop.App",
        "version": "8.0.0-rc.1.23420.5"
      }
    ],
    "configProperties": {
      "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true
    }
  }
}

*.csproj に要素を追加したアプリ

{
  "runtimeOptions": {
    "tfm": "net8.0",
    "frameworks": [
      {
        "name": "Microsoft.NETCore.App",
        "version": "8.0.0-rc.1.23419.4"
      },
      {
        "name": "Microsoft.WindowsDesktop.App",
        "version": "8.0.0-rc.1.23420.5"
      }
    ],
    "configProperties": {
      "Switch.System.Windows.Media.EnableHardwareAccelerationInRdp": true,
      "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true
    }
  }
}

runtimeconfig.template.json を追加したアプリ

{
  "runtimeOptions": {
    "tfm": "net8.0",
    "frameworks": [
      {
        "name": "Microsoft.NETCore.App",
        "version": "8.0.0-rc.1.23419.4"
      },
      {
        "name": "Microsoft.WindowsDesktop.App",
        "version": "8.0.0-rc.1.23420.5"
      }
    ],
    "configProperties": {
      "Switch.System.Windows.Media.EnableHardwareAccelerationInRdp": true,
      "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true
    }
  }
}

計画通り。要素の追加された *.runtimeconfig.json が出来上がりました。

*.csproj に要素を追加したアプリと runtimeconfig.template.json を追加したアプリで同じものになりました。

■ 試してみたい

実行してみます。

何もわからない......。RDP でのハードウェア アクセラレーション、効くのでしょうがどう確認しましょうか......?

C# 12 の新機能 まとめ

そろそろ C# 12 の時期なので予習を始めなければなりません。
そこで一通り機能を確認してました。

参照情報

learn.microsoft.com

プライマリ コンストラクター

インスタンス コンストラクター - C# | Microsoft Learn
クラスの型名の後ろに引数を書くとその引数をもつプライマリなコンストラクターが作成される。
以前から record で使えていましたが、C# 12 からは普通の class でも使えるようになった。ただし少し挙動が違う。

Console.WriteLine(new Test("saitama1").Name);  // こういう風に使える
// Console.WriteLine(new Test().Name);         // これは書けない 引数無しのコンストラクタは存在しない
// Console.WriteLine(new Test("saitama3").name);  // これは書けない 引数はプロパティにならない
Console.WriteLine(new TestNoArg().Name);       // 他のコンストラクター
Console.WriteLine(new TestRecord("saitama5").Name);
Console.WriteLine(new TestRecord("saitama6").name);   // record では引数はプロパティになる

// C# 12 の新機能
class Test(string name)  // 文字列一つを引数にとるコンストラクタ
{
    public string Name => name; // クラス内でプライマリ コンストラクタの引数が参照できる
}

// プライマリ コンストラクターの他にコンストラクターを作る場合は、this(...) でプライマリ コンストラクターを呼ぶ
class TestNoArg(string name)
{
    public TestNoArg() : this("saitama4") {; }   // 他のコンストラクターを作る場合は、this(...) でプライマリ コンストラクターを呼ぶ
    public string Name => name;
}

// record では以前から使えた
record TestRecord(string name)
{
    public string Name => name;
}

コレクション式

コレクション式 (コレクション リテラル) - C# | Microsoft Learn
コレクションの作成に new がいらなくなった。
コレクションの各要素を要素に持つコレクションを作成できるようになった。

{
    int[] a = [1, 2];           // こう書けるようになった
    int[] b = new[] { 3, 4 };   // これまではこう

    int[] c = [.. a, .. b];     // コレクションの各要素を要素に持つコレクション
    Console.WriteLine($"{c[0]}, {c[1]}, {c[2]}, {c[3]}");   // 1, 2, 3, 4
}
{ // List<T> でも OK
    List<int> a = [1, 2];           // こう書けるようになった
    List<int> b = new (){ 3, 4 };   // これまではこう

    List<int> c = [.. a, .. b];     // コレクションの各要素を要素に持つコレクション
    Console.WriteLine($"{c[0]}, {c[1]}, {c[2]}, {c[3]}");   // 1, 2, 3, 4
}

既定のラムダ パラメーター

ラムダ式 - ラムダ式と匿名関数 - C# | Microsoft Learn
default 値を持つ (省略可能な引数をもつ) ラムダ式が書ける。

var lambda1 = (int a, int b = 1) => a + b;  // 省略可能な引数を持つラムダ式が書ける
Console.WriteLine(lambda1(2));
Console.WriteLine(lambda1(2, 3));

Function lambda2 = (int a, int b = 1) => a + b; // delegate で型を書くことも
Console.WriteLine(lambda2(3));
Console.WriteLine(lambda2(3, 4));

public delegate int Function(int a, int b = 1);

任意の型の別名設定

using ディレクティブ - C# リファレンス - C# | Microsoft Learn
これまで using 別名設定ができなかった型が using 別名設定できるようになった。
unsafe な型もできるらしいですが、unsafe キーワードと using 別名設定の組み合わせのコードの書き方が思う付かず unsafe は試せていません。

using A = System.String;        // これまでもできていた別名の作成
using B = (string a, string b); // タプルの別名を作成できるようになった
using C = string[];             // 配列も

A a = "saitama";
B b = ("gunma", "ibaraki");
C c = ["chiba", "tochigi"];

Console.WriteLine($"{a}, {b.a}, {b.b}, {c[0]}, {c[1]}");    // saitama, gunma, ibaraki, chiba, tochigi

インライン配列

構造体型 - C# リファレンス - C# | Microsoft Learn
N 個の要素を持つ構造体を作れる。配列のように添え字アクセスできる。
配列と違って Linq が使えない。System.Span に代入できる。

using System.Reflection;

var buffer = new Buffer();
// 配列のように添え字アクセス可能
for (int i = 0; i < 10; ++i) buffer[i] = i;
foreach (var i in buffer) Console.WriteLine(i);

// サイズ
unsafe { Console.WriteLine(sizeof(Buffer)); Console.WriteLine(sizeof(Buffer20)); }  // 40 80

// 長さを取得
// 型 Buffer の長さは 10 です
// 型 Buffer20 の長さは 20 です
Console.WriteLine($"型 {typeof(Buffer)} の長さは {((System.Runtime.CompilerServices.InlineArrayAttribute?)typeof(Buffer).GetCustomAttribute(typeof(System.Runtime.CompilerServices.InlineArrayAttribute)))?.Length} です");
Console.WriteLine($"型 {typeof(Buffer20)} の長さは {((System.Runtime.CompilerServices.InlineArrayAttribute?)typeof(Buffer20).GetCustomAttribute(typeof(System.Runtime.CompilerServices.InlineArrayAttribute)))?.Length} です");

// System.Linq.Enumerable.Select(buffer, i => Console.WriteLine());  // これはできない。IEnumerable<T> を実装していない


// Span<T> に代入可能
System.Span<int> span = buffer;
// System.Span<int> span2 = new Buffer();  // これはエラー「 エラー CS9164 式は割り当て可能な変数ではないため、'Span<int>' に変換できません 」
Buffer20 b20 = new();
System.ReadOnlySpan<int> rospan = b20;
Console.WriteLine($"型 {typeof(Buffer)} の長さは {span.Length} です");      // 型 Buffer の長さは 10 です
Console.WriteLine($"型 {typeof(Buffer20)} の長さは {rospan.Length} です");  // 型 Buffer20 の長さは 20 です



// インライン配列の宣言
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
    private int _firstElement;
}

[System.Runtime.CompilerServices.InlineArray(20)]
public struct Buffer20
{
    private int _firstElement;
}

インターセプター

roslyn/docs/features/interceptors.md at main · dotnet/roslyn · GitHub
実験的機能という意味でのプレビュー機能。実践ではまだ使用が推奨されない。
メソッド呼び出しを別のメソッドに置き換える機能。使用時には .csproj に <Features>InterceptorsPreview</Features> 要素を追加する必要がある。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <Features>InterceptorsPreview</Features>
  </PropertyGroup>

</Project>

また、使用の際に必要となる System.Runtime.CompilerServices.InterceptsLocationAttribute 属性が見つからずエラーになるので、使用時に属性を作ります。※この方法が正しいのか分かりませんが......
エラー CS0246 型または名前空間の名前 'InterceptsLocationAttribute' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
エラー CS0246 型または名前空間の名前 'InterceptsLocation' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)

namespace System.Runtime.CompilerServices
{
    sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute { }
}

実際の動作確認コードは次のようになる。

Class c = new() { Name = "Tokai" };
c.Method("Saitama");    // Method::Name = Tokai, param = Saitama
c.Method("Gunma");      // Interceptor1::Name = Tokai, param = Gunma
c.Method("Ibaraki");    // Interceptor2::Name = Tokai, param = Ibaraki

class Class
{
    public string Name { get; set; }
    public void Method(string param) => Console.WriteLine($"Method :: Name = {Name}, param = {param}");
}


// インターセプター
static class Interceptor
{
    // Program.cs の中の 3 行目、3 文字目から始まるメソッド呼び出し ( Method("Gunma") ) を置き換える
    // this 引数はメソッドの所属するオブジェクト、第 2 引数は置き換え元のメソッドの引数
    [System.Runtime.CompilerServices.InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 3, character: 3)] // refers to the call at (L1, C1)
    public static void InterceptorMethod1(this Class c, string param) => Console.WriteLine($"Interceptor1 :: Name = {c.Name}, param = {param}");

    // Program.cs の中の 4 行目、3 文字目から始まるメソッド呼び出し ( Method("Ibaraki") ) を置き換える
    [System.Runtime.CompilerServices.InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 4, character: 3)] // refers to the call at (L2, C2)
    public static void InterceptorMethod2(this Class c, string param) => Console.WriteLine($"Interceptor2 :: Name = {c.Name}, param = {param}");
}

namespace System.Runtime.CompilerServices
{
    sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute { }
}

■ C# 12!

C# 12、かなり良い感じです。C# って本当に素晴らしいですね!!

Visual Studio の 17.7.4 がリリースされました

Visual Studio のアップデート 17.7.4 がリリースされました。

今回はセキュリティのアップデートがあります、すぐにアップデートしましょう。

■ 更新内容

■ 機能の追加

  • Git のバージョンが v 2.41.0.3 に更新

の追加があるようです。

問題の解決

  • ホット リロードでクラッシュ
  • プロセスハイジャックの防止

の対策があるようです。

Developer Community

  • これは内容を見ても何を言っているかよくわかりませんでした。どうも、UI のアニメーションによって UI の要素の位置がずれる - アニメーションが終了真するまで待てない、ということのような.違うような...... コード レンズ TypeScript/JavaScript 参照アニメーション
  • これは内容を見ても何を言っているかよくわかりませんでした。情報量が足りない...... Visual Studio が0xC0000005に対してアサートしない: アクセス違反

が対策されたようです。

セキュリティに関するアップデート

今回はセキュリティに関するアップデートが 7 件あります。

なるべく早くアップデート

今回はセキュリティに関するアップデートが 7 件あります。すぐにアップデートしましょう。

■ 更新方法

Visual Studio の更新はメニューの ツール > ツールと機能を取得 で開くインストーラーから行えます。

C# 12 を試す .NET 8 RC1 編

そろそろ C# 12 の時期なので予習を始めなければなりません。
今回は .NET 8 RC1 が出たので、その環境で C# 12 を書いてみます。

> dotnet --version
8.0.100-rc.1.23455.8

ちゃんと RC1 環境です。
プロジェクトを作っていきます。

> dotnet new console

出来上がったファイルです。

> ls
obj
ConsoleAppNetRC1.csproj
Program.cs

■ C# 12 のコードを書いてみる

作った C# 12 のコードを書いてみます。C# 12 のコードはこちらの記事から。
rksoftware.hatenablog.com

Program.cs

using System.Runtime.CompilerServices;

using MyType = (int x, int y);

// See https://aka.ms/new-console-template for more information
Console.WriteLine(new Test("Hello, World! 1").Name());

Console.WriteLine(new MyType(1, 2));

int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[] single = [.. row0, .. row1, .. row2];
foreach (var element in single)
{
    Console.Write($"{element}, ");
}

var lamda = (int a = 1) => a + a;
Console.WriteLine(lamda());

var buffer = new Buffer();
for (int i = 0; i < 10; i++)
{
    buffer[i] = i;
}

foreach (var i in buffer)
{
    Console.WriteLine(i);
}

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 Test(string name)
{
    public string Name() { return name; }
}

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
    private int _element0;
}

class C
{
    public void InterceptableMethod(int param)
    {
        Console.WriteLine($"interceptable {param}");
    }
}

// generated code
static class D
{
    [InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 36, character: 3)] // refers to the call at (L1, C1)
    public static void InterceptorMethod(this C c, int param)
    {
        Console.WriteLine($"interceptor {param}");
    }

    [InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 37, character: 3)] // refers to the call at (L2, C2)
    //[InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 38, character: 3)] // refers to the call at (L3, C3)
    public static void OtherInterceptorMethod(this C c, int param)
    {
        Console.WriteLine($"other interceptor {param}");
    }
}

namespace System.Runtime.CompilerServices
{
    sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute
    {
    }
}

.csproj は編集しないでやってみます。

■ .csproj は編集しない

> dotnet build
MSBuild のバージョン 17.8.0-preview-23418-03+0125fc9fb (.NET)
  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
Program.cs(63,6): error CS9137: 試験的な機能である 'interceptors' が有効になっていません。'<Features>InterceptorsPreview</Features>' をプロジェクトに追加します。
Program.cs(69,6): error CS9137: 試験的な機能である 'interceptors' が有効になっていません。'<Features>InterceptorsPreview</Features>' をプロジェクトに追加します。
ビルドに失敗しました。

ビルドに失敗しました。
しかし見てください、構文エラーはありません! インターセプターが実験的機能過ぎて設定が必要と言われているだけです。

ということで、インターセプターを消してみます。

using System.Runtime.CompilerServices;

using MyType = (int x, int y);

// See https://aka.ms/new-console-template for more information
Console.WriteLine(new Test("Hello, World! 1").Name());

Console.WriteLine(new MyType(1, 2));

int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[] single = [.. row0, .. row1, .. row2];
foreach (var element in single)
{
    Console.Write($"{element}, ");
}

var lamda = (int a = 1) => a + a;
Console.WriteLine(lamda());

var buffer = new Buffer();
for (int i = 0; i < 10; i++)
{
    buffer[i] = i;
}

foreach (var i in buffer)
{
    Console.WriteLine(i);
}

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 Test(string name)
{
    public string Name() { return name; }
}

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
    private int _element0;
}

class C
{
    public void InterceptableMethod(int param)
    {
        Console.WriteLine($"interceptable {param}");
    }
}

// generated code
static class D
{
//    [InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 36, character: 3)] // refers to the call at (L1, C1)
    public static void InterceptorMethod(this C c, int param)
    {
        Console.WriteLine($"interceptor {param}");
    }

//    [InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 37, character: 3)] // refers to the call at (L2, C2)
    //[InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 38, character: 3)] // refers to the call at (L3, C3)
    public static void OtherInterceptorMethod(this C c, int param)
    {
        Console.WriteLine($"other interceptor {param}");
    }
}

namespace System.Runtime.CompilerServices
{
    sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute
    {
    }
}

■ 計画通り

> dotnet build

ビルドに成功しました。
> dotnet run
Hello, World! 1
(1, 2)
1, 2, 3, 4, 5, 6, 7, 8, 9, 2
0
1
2
3
4
5
6
7
8
9
interceptable 1
interceptable 1
interceptable 2
interceptable 1

計画通り実行までできました。

■ 言語バージョン

今回、.csproj に <LangVersion>Preview</LangVersion> を書きませんでした。.NET 8 RC1 ではC# 12 はプレビュー機能ではなくなったという事ですね! 夢広がりますな~。

■ .NET 8 RC1

.NET 8 RC1 は Visual Studio のプレビュー版のアップデートでも入ります。素早くアップデートしましょう!

C# 12 の新機能の確認 一覧

そろそろ C# 12 の時期なので予習を始めなければなりません。
learn.microsoft.com

というわけでいろいろやった記事のまとめです。

■ 記事たち

rksoftware.hatenablog.com rksoftware.hatenablog.com rksoftware.hatenablog.com rksoftware.hatenablog.com rksoftware.hatenablog.com rksoftware.hatenablog.com rksoftware.hatenablog.com rksoftware.hatenablog.com rksoftware.hatenablog.com rksoftware.hatenablog.com rksoftware.hatenablog.com

■ そなえよう

まだ C# 12 までは少し時間はありますが、出たらすぐに使いますよね。備えましょう。

C# 12 の新機能の確認 「 インターセプター 」

そろそろ C# 12 の時期なので予習を始めなければなりません。
今回は 「 インターセプター 」 を確認してみましょう。

■ インターセプター

github.com
C# 12 がまだプレビューだからという理由を超えて実験的機能という意味でのプレビュー機能。
メソッド呼び出しを別のメソッドに置き換える機能。使用時には .csproj に <Features>InterceptorsPreview</Features> 要素を追加する。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <LangVersion>Preview</LangVersion>
    <Features>InterceptorsPreview</Features>
    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
  </PropertyGroup>

</Project>

また、使用の際に必要となる System.Runtime.CompilerServices.InterceptsLocationAttribute 属性が見つからずエラーになるので、使用時に属性を作ります。※この方法が正しいのか分かりませんが......
エラー CS0246 型または名前空間の名前 'InterceptsLocationAttribute' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
エラー CS0246 型または名前空間の名前 'InterceptsLocation' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)

namespace System.Runtime.CompilerServices
{
    sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute { }
}

実際の動作確認コードは次のようになりました。

Class c = new() { Name = "Tokai" };
c.Method("Saitama");    // Method::Name = Tokai, param = Saitama
c.Method("Gunma");      // Interceptor1::Name = Tokai, param = Gunma
c.Method("Ibaraki");    // Interceptor2::Name = Tokai, param = Ibaraki

class Class
{
    public string Name { get; set; }
    public void Method(string param) => Console.WriteLine($"Method :: Name = {Name}, param = {param}");
}


// インターセプター
static class Interceptor
{
    // Program.cs の中の 3 行目、3 文字目から始まるメソッド呼び出し ( Method("Gunma") ) を置き換える
    // this 引数はメソッドの所属するオブジェクト、第 2 引数は置き換え元のメソッドの引数
    [System.Runtime.CompilerServices.InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 3, character: 3)] // refers to the call at (L1, C1)
    public static void InterceptorMethod1(this Class c, string param) => Console.WriteLine($"Interceptor1 :: Name = {c.Name}, param = {param}");

    // Program.cs の中の 4 行目、3 文字目から始まるメソッド呼び出し ( Method("Ibaraki") ) を置き換える
    [System.Runtime.CompilerServices.InterceptsLocation(@"C:\Sample\cs12\ConsoleAppVSPreview\ConsoleAppVSPreview\Program.cs", line: 4, character: 3)] // refers to the call at (L2, C2)
    public static void InterceptorMethod2(this Class c, string param) => Console.WriteLine($"Interceptor2 :: Name = {c.Name}, param = {param}");
}

namespace System.Runtime.CompilerServices
{
    sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute { }
}