C# は Task を積極的に使っていったほうがいいのかも
JavaScript の Promise みたいなものが C# でもあって Task です
await で待機して結果を受け取れるあたりも一緒です

ただ Promise だとネストしても自動で 1 段にしてくれますが Task はそういうことがなく(
設定次第) 値を取り出すために二重三重に await をしないといけなかったりで Promise よりも使いづらかったです
また非同期処理を呼び出すところが Node.js ほどないのであまり使ってなかったです

JavaScript だとシングルスレッドでしか動かないので 不要なところを Promise 化しても 長い処理の途中で別の処理を行えるだけで全体としての実行時間は変わりません
ですが Task はマルチスレッドで動いてくれます
JavaScript でいう Worker を使うようなことを手軽にできます
この利点を考えたら普段から積極的に使っていく方がいいのかもしれない と思いました

例えばこういう処理があったとします

var start = DateTime.Now;

var list = new List<int>();

for (var i = 0; i < 12; i++)
{
list.Add(i);
}

var middle = DateTime.Now;
System.Console.WriteLine(middle - start);

var result = list.Select(x =>
{
var n = 0;
for (var i = 0; i < 100000000; i++)
{
n++;
}
return n;
}).ToList();

System.Console.WriteLine(string.Join(",", result));
var end = DateTime.Now;
System.Console.WriteLine(end - middle);
00:00:00.0076682
100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000
00:00:02.4401087

リストの要素のそれぞれに重たい処理を行います
今回だと 1 億回インクリメントです
全体で 2 秒ほどかかってます

これを Task 化します
リストのそれぞれの処理を Task にして最後にまとめて待機します

var start = DateTime.Now;

var list = new List<int>();

for (var i = 0; i < 12; i++)
{
list.Add(i);
}

var middle = DateTime.Now;
System.Console.WriteLine(middle - start);

var tasks = list.Select(x =>
Task.Run(() =>
{
var n = 0;
for (var i = 0; i < 100000000; i++)
{
n++;
}
return n;
})
);

var result = await Task.WhenAll(tasks);

System.Console.WriteLine(string.Join(",", result));
var end = DateTime.Now;
System.Console.WriteLine(end - middle);
00:00:00.0075865
100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000,100000000
00:00:00.3334272

0.3 秒程度になりました

別スレッドになるので JavaScript の Worker みたいに変数共有の面倒さがあったりするのかと思いましたが 普通に Task の中から外側の変数にアクセスできました
参照のみでなく更新もできます
各 Task が結果を返すのではなく Task 外の List に結果を追加するようしてみるとこんな感じです

var start = DateTime.Now;

var list = new List<int>();

for (var i = 0; i < 12; i++)
{
list.Add(i);
}

var middle = DateTime.Now;
System.Console.WriteLine(middle - start);

var result = new List<int>();

var tasks = list.Select((x, i) =>
Task.Run(() =>
{
var n = i;
for (var i = 0; i < 100000000; i++)
{
n++;
}
result.Add(n);
})
);

await Task.WhenAll(tasks);

System.Console.WriteLine(string.Join(",", result));
var end = DateTime.Now;
System.Console.WriteLine(end - middle);
00:00:00.0069215
100000000,100000001,100000003,100000002,100000004,100000006,100000005,100000008,100000007,100000009,100000010,100000011
00:00:00.3152148

ただ並列処理になるのでこういうことをすると結果の順番はバラバラです
あとから元データと紐付けるのが面倒なので Add ではなくインデックスを使って指定の場所を更新するか Select にしてそれぞれが結果を返す形でよさそうです
C# 12 のコレクション式記法が良さそう
.NET 8 が出ましたね
合わせて C# 12 が使えるようになったようです
新記法が追加されたようで 少しスクリプト言語に近づきました

int[] nums = [1, 2];

という感じで [] のリテラルで配列を作れます
JavaScript や PHP や Python などの言語に近い感じで書けます

配列以外のリストやセットなどでもいいです

List<string> strs = ["foo", "bar"];
HashSet<int> hs = [1, 2];

ネストもできます

int[][] n = [[1, 2], [2, 3]];
List<List<List<int>>> ll = [[[1], [2]], [[3], [4]]];

展開もできます

int[] ii = [.. i, .. i];

この記法は JavaScript や PHP と似てるようで違って紛らわしいです
... じゃなくて .. ですし フォーマットすると変数名の間にスペースができます
Range のリテラルに使う .. を使いまわしてるみたいです

var r = 1..10;

展開自体はこれまでの new [] { 1, 2 } の記法のときからできたのかと思いましたが できないようでした
今でもエラーになります

var error = new[] { ..i };

色々できて便利ですが Dictionary は対応していないようです

Dictionary<string, bool> flags = [["foo", false], ["bar", true]];

これはエラーでした

KeyValuePair を入れる方法も試しましたがダメでした

var kvp = new KeyValuePair<string, bool>("foo", false);
Dictionary<string, bool> flags = [kvp];

'value' の必要なパラメーター 'Dictionary<string, bool>.Add(string, bool)' に対応する特定の引数がありません
って言われます
Dictionary の場合は Add で key と value の 2 つの引数を取る形になりますが この記法で書くと KeyValuePair の 1 つを渡すようになってしまって引数が足りてないとみなされているようです

なので Dictionary を使う場合は まだこれまでの記法で書く必要があります

var flags = new Dictionary<string,bool> { ["foo"] = false, ["bar"] = true };

一見便利な新機能ですが 個人的に不満もあって var が使えません
左辺に型を明示的に書く必要があります
右辺だけだと List なのか配列なのかわからないので仕方ないのですが C 系の 「型を文の最初に書く」 記法は読みづらいので好きになれません
変数宣言だとわかるように 文は var など固定の文字列で始めたいです
int 等のシンプルな型ならまだいいですが 長めの型になるとこれが変数宣言だとわかるまで少し時間がかかって読みづらいです

一応 右辺でキャストすれば左辺は var でも通るのですが 無理矢理感もあってこれでいいのか不安な感じもします
これまでの記法とあまり変わらないですし

var nums = (int[])[1, 2];
var ll = (List<List<List<int>>>)[[[1], [2]], [[3], [4]]];
.NET Framework と .NET を混ぜて使う
.NET Framework で作られたレガシーなアプリがあって サポート期間とか色々な事情で .NET には移行しない
そのアプリの一部機能を使う別アプリを新規に作るけど 最新 LTS の .NET 6.0 を使いたい
そんなことできるのかなと思って試してみたら普通にできた

ソリューションの中に新規プロジェクトを作るとき .NET のプロジェクトを選ぶ
作ったプロジェクトの依存関係の設定でソリューション内から .NET Framework のアプリを選ぶ

あとは .NET Framework 側のメソッドを単純に呼び出すだけ

WindowsFormsApp1.Class1.something()

これで動いた

ただし WinForms のアプリの Form を開く機能はコンソールアプリからは呼び出せない
コンパイル時にエラーは出ないけど実行時にアセンブリが見つからないってエラーが出る
.NET Framework 側のプロジェクトのビルド時に依存アセンブリは考慮されてそうだけど 見つからないらしい

.NET 側もアプリの種類を WinForms にすれば問題なく開ける
.NET アプリから .NET Framework 経由で Form を開くと .NET Framework アプリでも WinForms のアセンブリは .NET 側の SDK のものが使われてるのかな?

コンソールアプリとしてプロジェクトを作ってしまった場合はプロジェクトのプロパティを開いて 「アプリケーション > 全般」 の部分で以下のように設定すれば使えるようになる

出力の種類 → Windows アプリケーション
ターゲット OS → Windows
Windows フォーム → チェック入れる

WPF なら Windows Presentation Foundation の方にチェック
WinForms のフォーカスがよくわからない
こういう構造のフォームを用意する

Form
TextBox1
UserControl1
Button1
TextBox2

Form の ActiveControl プロパティに form.userControl1 をセットしてフォーカスを確認する

System.Diagnostics.Debug.WriteLine(this.userControl1.CanFocus);
// True
this.ActiveControl = this.userControl1;

System.Diagnostics.Debug.WriteLine(this.ActiveControl);
// WinFormsApp1.UserControl1
System.Diagnostics.Debug.WriteLine(this.userControl1.Focused);
// False
System.Diagnostics.Debug.WriteLine(this.userControl1.ContainsFocus);
// False
System.Diagnostics.Debug.WriteLine(this.userControl1.ActiveControl);
// null

userControl1.CanFocus が True なのでフォーカスできるはず

ActiveControl に代入後には ちゃんと中身が UserControl1 になってる
なのに userControl1 の Focused や ContainFocus は False
userControl1 の ActiveControl は null
フォーカスが行方不明
見た目上は初期位置の TextBox1 のまま

もしかして JavaScript であるようなイベントループに処理を返してから変わったり?
タイマーで 1 秒待ってから同じものを表示するようにしてみる
→全く同じ結果

ActiveControl とフォーカスは別?

だけど

this.ActiveControl = this.userControl1.button1;

にして 代入直後の時点でさっきと同じ内容を表示すると

WinFormsApp1.UserControl1
False
True
System.Windows.Forms.Button, Text: button1

ContainsFocus が True になっていて userControl1 の ActiveControl が button1 になってる
見た目上でもボタンにフォーカスがあたってる

this.ActiveControl = this.textBox2;

でもフォーカスが切り替わってカーソル位置も変わってる

UserControl だけ特殊みたい


Focus メソッドも使ってみる

this.userControl1.Focus()

WinFormsApp1.UserControl1
False
True
System.Windows.Forms.Button, Text: button1

「this.userControl1.button1」 を ActiveControl に設定したときと一緒で UserControl1 内の最初のコントロールにフォーカスがあたってる
UserControl 自体にはフォーカス当たらなそう

だけど UserControl1 の中身を空にしてから

this.userControl1.Focus()

すると

WinFormsApp1.UserControl1
True
True
(null)

フォーカスあたってるみたい
画面上ではどこにもフォーカスあたってるような見た目はなし
C# で static とインスタンスのメソッド呼び出し
久々に C# 使って
static メソッドってどう呼び出せるんだっけ?
PHP の self:: (だったかな?) みたいな直接クラス名書かずに参照できたっけ?
と思って調べたら普通に何もなしでメソッド名だけで呼び出せた

でもたしかインスタンスのメソッドも this 省略できた気がする

> class A
. {
. static int foo() { return 1; }
. int bar() { return 2; }
. public void method() { Console.WriteLine(foo()); Console.WriteLine(bar()); }
. }
> new A().method()
1
2

foo() と bar() どっちも使える
ただ 呼び出し時に this のメソッドなのか static メソッドかわからないのはやっぱり分かりづらい気がする
this は書いてあってほしいけど static って this コンテキストと関係ない普通の関数みたいなものなので こっちは 「A.」 みたいなのなくてメソッド名だけでも良さそうかな
C# ってグローバル関数がなくて全部クラスのメソッドだからグローバル関数風に修飾なしで呼び出せなくて 関数名だけで呼び出ししてたらローカル関数か そのクラスの static メソッドって判断できるだろう (一応例外的な using static があるけどほぼ使わない)
C# で匿名型の空リストつくる
List つくるときにテンプレートの T を書かないといけないけど匿名型ってどう書くの?
書くのは無理そうなので推論にまかせる

方法は色々あって
◯ 匿名型の要素 1 つの配列から List 化して中身を消すとか
◯ 要素が 0 の Collection 型から Select で匿名型のリスト化するとか

どうやっても長くなるのは避けられないので楽に書けるようにしてみた

using System;
using System.Collections.Generic;
using System.Linq;

namespace cs01
{
class Program
{
static void Main(string[] args)
{
var list = emptyList(() => new { a = 0, b = "" });
list.Add(new { a = 1, b = "A"});
list.Add(new { a = 2, b = "B"});
System.Console.WriteLine(string.Join("-", list.Select(e => e.b)));
}

static List<T> emptyList<T>(Func<T> fn)
{
return Enumerable.Empty<object>().Select(_ => fn()).ToList();
}
}
}

実行結果

A-B

emptyList に匿名型を返すラムダ式を渡す
匿名型の実体を作らなくていいように関数を渡すようにしたけど 関数を作って実行するコストのほうが高そうだし直接匿名型の値を渡すようにしてもいいかも
動的にメソッド呼び出すと例外が InnerException になる
◯ 普通に呼び出し
◯ Action 型にメソッドを代入して Invoke で呼び出し
◯ Action 型にメソッドを代入して DynamicInvoke で呼び出し
◯ リフレクションで呼び出し

それぞれの方法でメソッドを呼び出す
呼び出されるメソッドは例外を発生させる

public void Button_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("standard call+++++++++");
try
{
this.method1();
}
catch (Exception ex)
{
Console.WriteLine($"catch at Button_Click: {ex.Message}");
Console.WriteLine($"-- inner exception: {ex.InnerException?.Message}");
}

Console.WriteLine("\ndelegate call+++++++");
try
{
Action fn = this.method1;
fn.Invoke();
}
catch (Exception ex)
{
Console.WriteLine($"catch at Button_Click: {ex.Message}");
Console.WriteLine($"-- inner exception: {ex.InnerException?.Message}");
}

Console.WriteLine("\ndelegate dynamic call+++++++");
try
{
Action fn = this.method1;
fn.DynamicInvoke(null);
}
catch (Exception ex)
{
Console.WriteLine($"catch at Button_Click: {ex.Message}");
Console.WriteLine($"-- inner exception: {ex.InnerException?.Message}");
}

Console.WriteLine("\nreflection call+++++++");
try
{
this.GetType().GetMethod("method1").Invoke(this, null);
}
catch (Exception ex)
{
Console.WriteLine($"catch at Button_Click: {ex.Message}");
Console.WriteLine($"-- inner exception: {ex.InnerException?.Message}");
}
}

public void method1()
{
try
{
method2();
}
catch (Exception ex)
{
Console.WriteLine($"catch method1: {ex.Message}");
Console.WriteLine($"-- inner exception: {ex.InnerException?.Message}");
throw;
}
}

public void method2()
{
throw new Exception("えらー");
}

結果は下 2 つの動的な呼び出しだと catch した例外は TargetInvocationException になってる
その InnerException に呼び出したメソッドで実際に発生した例外が入ってる

standard call+++++++++
catch method1: えらー
-- inner exception:
catch at Button_Click: えらー
-- inner exception:

delegate call+++++++
catch method1: えらー
-- inner exception:
catch at Button_Click: えらー
-- inner exception:

delegate dynamic call+++++++
catch method1: えらー
-- inner exception:
catch at Button_Click: 呼び出しのターゲットが例外をスローしました。
-- inner exception: えらー

reflection call+++++++
catch method1: えらー
-- inner exception:
catch at Button_Click: 呼び出しのターゲットが例外をスローしました。
-- inner exception: えらー
C# の例外の投げ直しは throw だけ
C# で例外を catch したときにログするなどして もっかい throw するときは throw だけで例外を指定するとダメ

private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
this.method_1(); // Line.33
}
catch(Exception ex)
{
Console.WriteLine("method_1********");
Console.WriteLine("message:");
Console.WriteLine(ex.Message);
Console.WriteLine("stacktrace:");
Console.WriteLine(ex.StackTrace);
}

Console.WriteLine("");

try
{
this.method_2(); // Line.48
}
catch (Exception ex)
{
Console.WriteLine("method_2********");
Console.WriteLine("message:");
Console.WriteLine(ex.Message);
Console.WriteLine("stacktrace:");
Console.WriteLine(ex.StackTrace);
}
}

private void method_1()
{
try
{
exceptionMethod();
}
catch (Exception ex)
{
throw ex; // Line.68
}
}

private void method_2()
{
try
{
exceptionMethod();
}
catch (Exception)
{
throw; // Line.80
}
}

public void exceptionMethod()
{
throw new Exception("えらー"); // Line.86
}

throw ex で例外指定と throw だけのときでスタックトレースが変わる

method_1********
message:
えらー
stacktrace:
場所 samproj.Window4.method_1() 場所 C:\Users\user\Documents\Visual Studio 2015\Projects\samproj\proj02\Window4.xaml.cs:行 68
場所 samproj.Window4.Button_Click(Object sender, RoutedEventArgs e) 場所 C:\Users\user\Documents\Visual Studio 2015\Projects\samproj\proj02\Window4.xaml.cs:行 33

method_2********
message:
えらー
stacktrace:
場所 samproj.Window4.exceptionMethod() 場所 C:\Users\user\Documents\Visual Studio 2015\Projects\samproj\proj02\Window4.xaml.cs:行 86
場所 samproj.Window4.method_2() 場所 C:\Users\user\Documents\Visual Studio 2015\Projects\samproj\proj02\Window4.xaml.cs:行 80
場所 samproj.Window4.Button_Click(Object sender, RoutedEventArgs e) 場所 C:\Users\user\Documents\Visual Studio 2015\Projects\samproj\proj02\Window4.xaml.cs:行 48

throw だけだと元のを保持してくれる
throw ex にすると再度 throw したところになるので正確な場所がわからなくなる

C# interactive で拡張メソッドが作れない
csi の中でクラスを定義するとインナークラスになってる
Microsoft (R) Visual C# インタラクティブ コンパイラ バージョン 1.3.1.60616
Copyright (C) Microsoft Corporation. All rights reserved.

詳細については、「#help」と入力します。
> class C {}
> typeof(C).IsNested
true
IsNested が true

普通の実行だと
class Program
{
static void Main(string[] args)
{
Console.WriteLine(typeof(Program).IsNested);
Console.ReadKey();
}
}
// False
IsNested が false

拡張メソッドはトップレベルの静的クラスじゃないとダメらしい
> public static class Extension { public static int exm(this int i) { return i; } }
(1,51): error CS1109: 拡張メソッドは、トップ レベルの静的クラスで定義される必要があります。Extension は入れ子にされたクラスです。
C# でスクリプト実行
NuGet で Microsoft.CodeAnalysis.CSharp.Scripting をインストールする

using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private async void Button_Click(object sender, RoutedEventArgs e)
{
var runner = new RSRunner();
var state = await runner.run("var x = plusOne(1);");
MessageBox.Show(state.ReturnValue?.ToString());
var state2 = await runner.runContinue("WpfApp1.MainWindow.sub(100, x)", state);
MessageBox.Show(state2.ReturnValue?.ToString());
}

static public int sub(int x, int y)
{
return x - y;
}
}

public class RSRunner
{
public class Context
{
public int plusOne(int i)
{
return i + 1;
}
}

public Task<ScriptState<object>> run(string code)
{
var option = ScriptOptions.Default.WithReferences(
typeof(WpfApp1.MainWindow).Assembly
);
var context = new Context();

return CSharpScript.RunAsync(code, option, context);
}

public Task<ScriptState<object>> runContinue(string code, ScriptState<object> prestate)
{
return prestate.ContinueWithAsync(code);
}
}
}

ボタンを押すと
var x = plusOne(1);
が実行される
context に設定したオブジェクトのメソッドをグローバル関数みたいに使える
代入文なので結果を表示しても空

次に
WpfApp1.MainWindow.sub(100, x)
が実行される
メインのアプリのアセンブリを参照してるので WpfApp1.MainWindow.sub メソッドを使える
前の実行結果の state を使って途中から実行してる
x は前回実行した 1 + 1 の結果なので 98 が表示される
.NET Framework 関係のファイルの場所
msbuild
C:\Windows\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe
C:\Windows\Microsoft.NET\Framework\v3.5\MSBuild.exe
C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\MSBuild.exe
C:\Windows\Microsoft.NET\Framework64\v3.5\MSBuild.exe
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe
C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe
C:\Program Files (x86)\Microsoft Visual Studio\2017\WDExpress\MSBuild\15.0\Bin\MSBuild.exe

コマンドラインからビルドするツール

14.0 は VisualStudio 2015
15.0 は VisualStudio 2017
Community と WDExpress は VS のエディションの違い


framework
C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\
C:\Program Files\Reference Assemblies\Microsoft\Framework\
C:\Windows\Microsoft.NET\Framework\
C:\Windows\Microsoft.NET\Framework64\

.NETFramework の dll


gac
C:\Windows\assembly\
C:\Windows\Microsoft.NET\assembly

グローバルアセンブリキャッシュ
4.0 以降が Microsoft.NET の方


csi
C:\Program Files (x86)\MSBuild\14.0\Bin\csi.exe
C:\Program Files (x86)\Microsoft Visual Studio\2017\WDExpress\MSBuild\15.0\Bin\Roslyn\csi.exe
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\Roslyn\csi.exe

C# の REPL ツール


fsi
C:\Program Files (x86)\Microsoft SDKs\F#\4.1\Framework\v4.0\fsi.exe
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\FSharp\fsi.exe

F# の REPL ツール
C# の DateTime.Today
C# の DateTime 型に Today なんてあったんだ

いつも

DateTime.Now.Date

って書いてたけどそんな便利なのもあったなんて

ただ DateTimeOffset 型になるとないので注意
まあ C# だと無いプロパティだと IDE のエラーが出てすぐわかるけどね
C# の読み取り専用
Property
getter のみで setter をつけないことで読み取り専用にできる
> class C { public int foo { get; } = 1; }
> var c = new C();
> c.foo
1
> c.foo = 2
(1,1): error CS0200: プロパティまたはインデクサー 'C.foo' は読み取り専用であるため、割り当てることはできません
> typeof(C).GetProperty("foo").SetValue(c, 10)
Property Set メソッドが見つかりません。
> typeof(C).GetField("<foo>k__BackingField", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(c, 100)
> c.foo
100

リフレクションでも setter が存在しないので書き込めない

内部的には自動でフィールドを作って読み書きしてるので そのフィールドにさえアクセスできれば変更は可能
つまりリフレクションで書き換えできる




Constant
読み取り専用というより「定数」
一部の型のみ設定可能でオブジェクトなどはできない
> class C2 { public const int foo = 1; }
> typeof(C2).GetField("foo", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).SetValue(null, 100)
定数フィールドを設定できません。

扱いは Static なフィールド
リフレクションでも変更できない

byte code にハードコーディングされてるとか
stackoverflow では デコンパイルして編集して再度コンパイルすればいける みたいな回答もあった
実質無理ってことみたい



Field
readonly をつければ読み取り専用にできる
> class C3 { public readonly int foo = 1; }
> var c3 = new C3();
> c3.foo = 20
(1,1): error CS0191: 読み取り専用フィールドに割り当てることはできません (コンストラクター、変数初期化子では可 )。
> typeof(C3).GetField("foo", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).SetValue(c3, 100)
> c3.foo
100

リフレクションすれば普通に変更できる

VS の EditContinue
ちょっと古めの Framework バージョンで C# 動かしていたときのこと

アプリケーションのデバッグ中というのに気付かず ソース書き換えようとしたら

エディットコンテニュー ができるのは 4.5.1 からです

というメッセージが


Chrome のデバッガみたいに実行中にソースが書き換えれるのかな?
Roslyn のスクリプト機能付いた頃のバージョンぽいし そういうことできそう


だけど普段実行中に書き換えようとしたら「実行中です」みたいな警告ダイアログが出てた気もする