C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

C#, XAML: Windows 10 の Ligth/Dark テーマ状態を確認してそれっぽいテーマを採用するアプリにする方法

// Windows のテーマが Light か Dark か取得するヘルパー
using Microsoft.Win32;
namespace usagi.example
{
  static public class ThemeHelper
  {
    const string WindowsThemeRegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
    const string WindowsThemeRegistryValueName = "AppsUseLightTheme";

    public static bool IsWindowsThemeLight
    {
      get
      {
        var key = Registry.CurrentUser.OpenSubKey( WindowsThemeRegistryKeyPath );
        if ( key == null )
          return true;
        var v = (int)key.GetValue( WindowsThemeRegistryValueName );
        return v > 0;
      }
    }
  }
}
// App 起動時に MyLightTheme または MyDarkTheme を適用
namespace usagi.example
{
  public partial class App: Application
  {
    ResourceDictionary RD { get; } = new ResourceDictionary();
    
    App()
    {
      var theme = ThemeHelper.IsWindowsThemeLight ? "MyLightTheme" : "MyDarkTheme";
      RD.Source = new Uri( $"pack://application:,,,/Themes/{ theme }.xaml" );
      Resources.MergedDictionaries.Add( RD );
    }
  }
}
  • MyLightTheme, MyDarkTheme は .xaml を自分で作らなくても NuGet や github で探して適当なテーマを使うと楽。
  • 複数の .xaml に定義が分かれる場合は rd を配列や List にして用意して Resources.MergedDictionaries.Add するとよい。

References

  1. Dark Theme in WPF – RandomEngy's Tech Blog
  2. WPFアプリケーションの外観をWPFテーマで動的に変更するには? - @IT
  3. ResourceDictionary Class (System.Windows) | Microsoft Docs
  4. Registry Class (Microsoft.Win32) | Microsoft Docs

C#: C# コードから ECMAScript ソースを翻訳&実行する処理系を組み込む方法; WebBrowser, JScript, V8 ( ClearScript )

いくつか手段がある。

  1. ECMAScriptMicrosoft 処理系の1つ ChakraCore を内包する WPFWebBrowser コンポーネントを組み込む方法
  2. ECMAScriptMicrosoft 処理系の1つ JScript を組み込む方法
  3. ECMAScriptGoogle 処理系の V8 を組み込む方法
  4. ECMAScript 処理系を内包する node.js を組み込む方法

など他にもいくらか。

今回は、 WebBrowser, JScript, V8 ( ClearScript ) について簡単な ECMAScript コードを評価する方法を試したので以下にメモを残します😃

1. WebBrowser を使う方法

  • どこかに見えない WebBrowser を配置しておいて裏でこっそり使うなり、コードだけで new して使うなり。
  • お好みの ECMAScript を記述した html を Navigate して InvokeScript †1 する。
    • html はメモリーストリームや string から読ませるのは少し面倒なのでリソースに持っておいて UriNavigate するのが楽。
  • WinForm 版と WPF 版は似たような設計ながら互換性が無いのでいんたーねっつの情報には注意。

C#ECMAScript を実行する方法をぐぐるとたいていこの類似手法が出てくる。この方法はコード例とか示すまでもないかなーと思うし、めんどくさいので実装例は省略。

2. JScript を組み込む方法

  • プロジェクトの References へ Microsoft.JScript Microsoft.CSharp を追加。
  • ↓な感じで使う。ちょっとめんどくさい。
using System.CodeDom.Compiler;
// 任意の ECMAScript を評価できそうなチート的なメソッドを翻訳しておいて
// ユーザーから任意の ECMAScript ソースを受け取って実行結果を取得できるつもりの JScript 実装
var r = 
  ( CodeDomProvider
    .CreateProvider( "JScript" )
    .CompileAssemblyFromSource
      ( new CompilerParameters() { GenerateInMemory = true }
      , @"package _{ class _{ function f( a ) { return eval( a ); } } }"
      )
    .CompiledAssembly
    .CreateInstance( "_._" )
    as dynamic
  ).f( " 1 + 2 + 3 - 4 " )
  // ↑ こういう数値計算とか文字列処理の式がユーザーから飛んでくる分には期待動作します。
  ;
MessageBox.Show( r.ToString() );

dynamic とかたまには使ってみたくなったので使った。 dynamic しない場合は記述量は増えるが、

// assembly を一端保持しておいて
var a = CodeDomPrivider. /* 中略 */ .CompiledAssembly();
// 型(=JScriptのソース定義における `class` )を取得して
var t = a.GetType( "_._" );
// 型のインスタンスを取得して
var i = Activator.CreateInstance( t );
// 型のメンバー・メソッドを実行
var r = t.InvokeMember( "f", System.Reflection.BindingFlags.InvokeMethod, null, i, new object[] { " 1 + 2 + 3 - 4 " } );
// 結果を表示
MessageBox.Show( r.ToString() );

のように実装する事になる。

この実装方法では、任意の ECMAScript コードをユーザーから受け取って実行できそうに視えたかもしれないが、できない。例えば、ユーザーに "function ( a ) { return a * a; }" のように関数オブジェクトのソースを与えさせて、 r.f( 10 ) のように実行して使おうとすると例外で死ぬ。

ユーザーに関数を定義させたい場合は、

// ユーザー入力の想定
var source = @"( a, b ) { return Math.sqrt( ( a * a ) + ( b * b ) ); }";

のような関数の引数と本体の定義を受けて、

// CompileAssemblyFromSource にはこんな具合で包んで関数 f という事にして翻訳させておいて
$"package _{{ class _{{ function f { source } }} }}"

それで使うとか、そういう事になる。

私は JScript には興味がかなり希薄なのでよく知りませんが、少なくとも現在のそれは、 .netソースコード記述言語の1つであり、 C#, VB.net, F# とおそらくほぼ等価な実装を ECMAScript 派生の言語処理系として実装したもの、あるいはそんなようなものなのでしょう。

そういうわけで、 Microsoft 環境での C# 的にはこの方法は ECMAScript らしきスクリプト言語処理系をアプリへ組み込む方法としては、ある意味ではもっとも楽なものの1つかもしれません。ある意味では。

昔から一部界隈にはたいへん忌み嫌われて来た JScript の独自仕様や新しい ECMAScript 標準への追従性、 ChakraCore が廃止されたら処理系はどうなるのか、何かと面倒事の原因を抱え込む危惧をひしひしと感じるので、私はあまり使いたくありませんけどね♥

要求が ECMAScript ではなく、明確に JScript な場合にはもちろんこの方法は最適と思います。また、この方法と同様にして C#, VB.net ソースのスクリプト処理系や、実行時に .net な DLL を翻訳してリンクするとかそういう荒業が必要な場合の参考にはなるかもしれません。

V8 を組み込む方法

  • プロジェクトの References へ NuGet から v8.redist-v141-x64 あるいは開発環境にあわせたそういうのを追加。
    • NuGet から "v8" で検索するとたくさんでてくる。 x86, x64, v140(=vs2015), v141(=vs2017), v120, symbols, redist, full などの組み合わせ。

で簡単に使えるかと思ったのだけど、 icui18n の参照が無いとかで NuGet からの install は失敗するようだった。

f:id:USAGI-WRP:20190116234427p:plain

redist ではなく full でも同様だったし、どこの誰がどうパッケージングした icu が欲しいのか探すのも面倒なので、今回はそもそも別系統の V8 の NuGet パッケージを試す事にした。

  • プロジェクトの References へ NuGet から Microsoft.ClearScript を追加。
    • Microsoft 製の V8 処理系を .net へ組み込むライブラリーらしい。( JScript 互換っぽい表記もあるのが懸念点じゃが…)

f:id:USAGI-WRP:20190116235031p:plain

  • ↓な感じで使う。
using Microsoft.ClearScript.V8;
// 単純な加減算の式を評価する例
var e = new V8ScriptEngine();
var r = e.Evaluate( " 1 + 2 + 3 - 4 " );
MessageBox.Show( r.ToString() );

こんだけ😃

// 関数を評価して dynamic で受けておいて C# 実装側でその関数を実行する例
var e = new V8ScriptEngine();
dynamic f = e.Evaluate( " ( a, b ) => Math.sqrt( a * a + b * b ) " );
var r = f( 3, 4 );
MessageBox.Show( r.ToString() );

わーい簡単だ😃 それにナウい感じの Arrow も使える。さすが愛され続けて頭おかしい高速化と進化を遂げるに至った V8 さんです。

欠点は V8 のアッセンブリー追加によるフットプリントの増加がちょこっと大きいくらいかな。 ClearScript の DLL は 445KB 。参考として JScript の DLL は .net Framework の一部な上、 129KB です。PC向けでは気にするシーンは滅多に無いかもしれません。安心安全で簡潔に使える V8 処理系の恩恵の方がよほど大きいと思います。

ちなみに、 "string".substr(-1) を評価した結果は "g"でした。 Stackoverflow によるとこの挙動は JScript の仕様では "string" になるらしいので試してみましたが、先の JScript でも "g" が得られてしまうのでバグとして修正されたか ECMAScript へ準拠するよう JScript の仕様が修正されたのかもしれません。 †2

また、 ClearScript は Microsoft 製らしさの恩恵と思える点に、 ConsoleWPFコンポーネントなどホスト環境の何かしらを束縛して扱う仕組みが実装されている点も便利が良いかもしれません。そこは私もまだ試していませんので機会があればメモを書こうと思います。†3

参考

XAML: DataContext へ Properties/MySettings.settings を XAML だけで完結して束縛する方法

前提: Visual Studio 2017 でプロジェクトを作成してWPFアプリをデザイナーを使って作る際のはなし。

1. はじめに。 C# コードで束縛する場合

// 例えば MainWindow の DataContext へ
// MainWindow の ctor で Properties/MySettings.settings に
// 用意した MySettings.settings を束縛する場合
namespace MyApp
{
  public partial class MainWindow: Window
  {
    public MainWindow()
    {
      InitializeComponent();
      DataContext = Properties.MySettings.Default; // <-- ここ
    }
  }
}

MyApp プロジェクトへ Properties/MySettings.settings として用意(作成)した .settings のそれは、 C# ソースコードでは Properties 名前空間MySettings クラスの Default プロパティーget アクセサーからアクセスできる。

どうしてそれでアクセスできるかは Solution Explorer で用意(生成)した Properties/MySettings.settings のツリーを もう1段階だけ展開すると視えるようになる MySettings.Designer.cs のソースを読むとわかる。

コードで束縛する場合の問題点

C# コードで束縛を書くと非常に明快で簡潔な記述で済むが、 実行時まで評価されないので XAML のデザイナー上で、 BackgroundHeight など意匠設計に強く関わる初期値を 扱いたい場合に色なら真っ白、数値なら 0 など悲しい値が仮定された状態の デザイナーと戦う事になってしまう😅

2. ほんだい。XAML だけで完結して束縛する方法

a. ごく一部の値だけ直接束縛できればよい場合

一応もののついで、比較というほどでもないが例示しておくことに。

<Window
  xmlns:P="clr-namespace:MyApp.Properties"
  ...
>
  ...
  <TextBlock Text="{Binding MyPropertyKey,Source={x:Static P:MySettings.Default}}"/>

b. DataContext として束縛した上で使いたい場合

ほんだいのほんだい。

<Window
  xmlns:P="clr-namespace:MyApp.Properties"
  ...
  DataContext="{Binding Source={x:Static P:MySettings.Default}}"
>
  ...
  <TextBlock Text="{Binding MyPropertyKey}"/>

何れの場合も、 XAML を編集する前に MySettings.settings (厳密には MySettings.Designer.cs )が翻訳されていれば、 波線など出ず、また XAML のデザイナー上でもさっそく MySettings で定義済みのキーへのインテリセンス補完と 値の参照が有効になる。

BackgroundHeight などに MySettings の値が束縛された状態でプレビューされ、 .setting を DataContext として束縛する場合の XAML のデザインタイムにおける"期待動作" が得られ快適になる😃

C#: generics<T> + object 黒魔法 vs. 非ジェネリクスメソッド 実行速度編

前回の記事「C#: 組み込みの数値型を扱いたい generics の 型と IConvertible の肝っぽい事、それと object 型を暗躍させる必要性のメモ」で C#generics数値計算を実装すると結局 object の暗躍が必要なので実行時コストが気になるよ、でもまあ実行コストを気にしない何かならプログラマー=サンが楽にすめばそれはそれでいいんじゃない、とか適当な事を書いた。

適当な事を書いたので気になって2018年に意識が束縛されたまま2019年になれないまま事故って死んだら嫌だなーとか思ったのでベンチマーキングしてみた😃

// 1. 非ジェネリクス版
    public static decimal PlusOneD( decimal a )
      => a + 1M;

    public static int PlusOneI( int a )
      => a + 1;

// 2. ジェネリクス版だけど特定の型だけに実装を絞って object の介入コストを評価したい版
    public static T PlusOneGD<T>( T a )
      => (T)(object)( (decimal)(object)a + 1M );

    public static T PlusOneGI<T>( T a )
      => (T)(object)( (int)(object)a + 1 );

// 3. 見よ、これが C# のジェネリクスの限界というものだ!版
//(C#イケメン的には本質的に設計と実装方針が間違ってるというのはオイトケ、ベンチマーキング用なのだ)
    public static T PlusOneGR<T>( T a )
    {
      switch ( (object)a )
      {
        case byte v: return (T)(object)(byte)( v + 1 );
        case sbyte v: return (T)(object)(sbyte)( v + 1 );
        case Int16 v: return (T)(object)(Int16)( v + 1 );
        case UInt16 v: return (T)(object)(UInt16)( v + 1 );
        case Int32 v: return (T)(object)(Int32)( v + 1 );
        case UInt32 v: return (T)(object)(UInt32)( v + 1 );
        case Int64 v: return (T)(object)(Int64)( v + 1 );
        case UInt64 v: return (T)(object)(UInt64)( v + 1 );
        case float v: return (T)(object)(float)( v + 1 );
        case double v: return (T)(object)(double)( v + 1 );
        case decimal v: return (T)(object)(decimal)( v + 1 );
      }
      return a; // <- 例外吐くべきなんだけど、ベンチマーキング用にry
    }

// 4. ↑のやつの未対応型へ例外飛ばす版
// (実際のところ例外は飛ばさないし、 try ってるわけではないのだけどまあ、一応気になったので確認用)
    public static T PlusOneGR<T>( T a )
    {
      switch ( (object)a )
      {
        case byte v: return (T)(object)(byte)( v + 1 );
        case sbyte v: return (T)(object)(sbyte)( v + 1 );
        case Int16 v: return (T)(object)(Int16)( v + 1 );
        case UInt16 v: return (T)(object)(UInt16)( v + 1 );
        case Int32 v: return (T)(object)(Int32)( v + 1 );
        case UInt32 v: return (T)(object)(UInt32)( v + 1 );
        case Int64 v: return (T)(object)(Int64)( v + 1 );
        case UInt64 v: return (T)(object)(UInt64)( v + 1 );
        case float v: return (T)(object)(float)( v + 1 );
        case double v: return (T)(object)(double)( v + 1 );
        case decimal v: return (T)(object)(decimal)( v + 1 );
      }
      throw new NotImplementedException(); // <-- 吐くぞぉー(実際には今回はたどり着かない
    }

そんでもって、

// 簡易ベンチマーキングメソッド、その名も Benchmark 君
    double Benchmark<T>( T v, Func<T, T> f )
      where T : IComparable<T>
    {
      var t0 = DateTime.UtcNow;
      T a = (T)Convert.ChangeType( 0, typeof( T ) );
      do
        a = f( a );
      while ( a.CompareTo( v ) < 0 );
      return ( DateTime.UtcNow - t0 ).TotalMilliseconds;
    }

↑で、計測する↓

      var result = string.Empty; // 結果をちまちま記録していく
      {
        var t = 10_000_000M; // <-- decimal
        Benchmark( t, PlusOneD ); // <-- CPU あたため兼、キャッシュ不利など簡易的に回避する用
        var a = Benchmark( t, PlusOneD );
        var b = Benchmark( t, PlusOneGD<decimal> );
        var c = Benchmark( t, PlusOneGR<decimal> );
        var d = Benchmark( t, PlusOneGE<decimal> );
        result += $"decimal: {(a, b, c, d)}\n";
      }
      {
        var t = 10_000_000; // <-- int
        Benchmark( t, PlusOneI );
        var a = Benchmark( t, PlusOneI );
        var b = Benchmark( t, PlusOneGI<int> );
        var c = Benchmark( t, PlusOneGR<int> );
        var d = Benchmark( t, PlusOneGE<int> );
        result += $"    int: {(a, b, c, d)}\n";
      }

結果、こんなんでました。

Type <1> ms <2> ms <3> ms <4> ms
decimal 238.598 243.0946 358.6147 385.4431
int 26.007 27.0174 134.0307 133.547
float 65.0145 65.5082 163.5449 171.1108
double 86.0193 89.0203 190.0418 175.0401

float シリーズと double シリーズも同様の実装を加えてみました。

↑この結果を型ごとに vs. <1> 規準に正規化するとこんな感じ↓

Type <1> vs. <1> <2> vs. <1> <3> vs. <1> <4> vs. <1>
decimal 1.00 1.02 1.50 1.62
int 1.00 1.04 5.15 5.14
float 1.00 1.01 2.52 2.63
double 1.00 1.03 2.21 2.03

↓わかりやすいエクセルスクショ

f:id:USAGI-WRP:20181230022824p:plain

それぞれ何だったかおさらいすると

  • <1>: 非ジェネリクス
  • <2>: ジェネリクスだけど特定の型だけに対応して <T>object の介入コストを見たい版
  • <3>: <T>object にして switch して型マッチングして計算して object を介して <T> にして返す版(但し非対応型は何もせず返す)
  • <4>: <3> の非対応型には NotImplementedException を投げつける版

で、わかった事は、

  1. C# で数値表現の組み込み型については <T> の介入コストや object の介入コストは考える必要は無い
    • カリカリチューニングレベルで考える必要がある時はそもそも C# 使わない選択肢を考えた方がいいと思う😅)
  2. switch で型マッチングかけるとそれなりに重くなる
    • (と、言ってもカリカリチューニングの数値計算でもなければ人間には気にする事すらできない程度の性能差だろう)
    • (そんでもって、カリカリチューニングを C# の"上"でやる必要があるとしたらだたのコードゴルフみたいなものなのでまあどうでもいっかなっと😅)
  3. (おまけ)実行されない場所には例外スローを書いてもアッセンブリーレベルでの実行時の速度コスト増は気にしなくて良さそうだ
    • (もちろん try ブロックや実際に例外オブジェクトを飛ばす処理が実行される場合は常識的に考えてコストが増えるがそれはまた別のお話)

以上は処理系に寄るかもしれんというところはたぶんそうなのだけど、今回の記事ではとりあえず Microsoft .net Framework 4.7.2 の C# 7.0 でのはなし、という事で。実行は Release ビルド、 CPU は Ryzen Threadripper 2990WX のうちの主力開発機ちゃん(かわいい2018年の新型ちゃん♥)です。

C#: 組み込みの数値型を扱いたい generics の <T> 型と IConvertible の肝っぽい事、それと object 型を暗躍させる必要性のメモ

// 前提; この記事の Fail とか AreEqual とかはこいつの↓
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
// 任意の IConvertible を別の IConvertible に数値を保持して変換したい場合
    public static B C<A, B>( A a )
      where A : IConvertible
      where B : IConvertible
      => (B)Convert.ChangeType( a, typeof( B ) );

↑の単体試験は↓な感じになる。

      // C-1
      AreEqual( 123f, C<decimal, float>( 123m ) );
      // C-2
      try { C<byte, sbyte>( 200 ); }
      catch ( OverflowException ) { }
      catch { Fail(); }

IConvertibleConvert.ChangeType による変換では、

  • 値を保持して変換
  • 変換先で(ほぼ)等価な表現が不可能な場合は例外を飛ばす
    • overflow, underflow になる場合 -> System.OverflowException
    • +∞, -∞, NaN を対応する表現の無い int などへ変換した場合 -> System.OverflowException
  • 変換先に +∞, -∞ がある場合、対応する等価な数値表現が無くても「例外は飛ばない」

てな挙動。最後のやつ、ちょっと怖いね。単体試験にするとこういう事↓

      // C-3
      var a = 1.0e+100d;
      var b = C<double, float>( a );
      AreEqual( float.PositiveInfinity, b );
      AreNotEqual( a, b );

ここまで、 IConvertible の話。

ここから、 object の暗躍が必要な話。

// この実装自体には、意味はないんだけどさ。 object の暗躍が要るの。
    public static T PlusOne<T>( T a )
      where T : IConvertible
    {
      switch ( (object)a )
      {
        case byte v: return (T)(object)(byte)( v + 1 );
        case sbyte v: return (T)(object)(sbyte)( v + 1 );
        case Int16 v: return (T)(object)(Int16)( v + 1 );
        case UInt16 v: return (T)(object)(UInt16)( v + 1 );
        case Int32 v: return (T)(object)(Int32)( v + 1 );
        case UInt32 v: return (T)(object)(UInt32)( v + 1 );
        case Int64 v: return (T)(object)(Int64)( v + 1 );
        case UInt64 v: return (T)(object)(UInt64)( v + 1 );
        case float v: return (T)(object)(float)( v + 1 );
        case double v: return (T)(object)(double)( v + 1 );
        case decimal v: return (T)(object)(decimal)( v + 1 );

      }
      throw new NotImplementedException();
    }

やってる事は簡単、 IConvertible を実装する数値表現の組み込み型なら何でも 1 足しちゃうだけ。これこそまさにジェネリック!って感じだよね。(よく訓練された C#er は「C#ジェネリックはそういう事しない。型ごとちゃんと分けて実装書きましょう💪💪💪」とかたぶん言うんだろーけど。)

とりま、↑コレ、見ての通り、あちこちに object を暗躍させなくちゃならない。 C++er が見たら「テンプレートwwwwwwwとはwwwww」って吹き出したまま衝撃で死んでしまうかもしれない。

// だって C++er これで終わるもん。
template< typename T >
static T Experiment( const T a )
{ return a + 1; }

C++ だと型チェックきちんとしたいってのも C#IConvertible 制約より正確に数値表現の型だけカタカタ(≃テンプレート・メタ・プログラミング)もできるし、実行時に switchobject の型判定して…なんてプログラマーが疲弊する上にも実行時コストもかかるようなエネルギーの無駄遣いをしなくていい。(他にエネルギー使うのではとかそういう事は今回の記事の範囲外😅)

で、既に「じぇねりっくとは・・・」って思いでいっぱいになっているところへ追い打ち。

例えば、 T=byte の場合でも T f()returnbyte を返すためには、 return (T)(object)(byte)(123)object に暗躍してもらわないとならない。

じぇねりっくとは・・・ T ・・・ こんぱいる・・・げんごしょぅ・・・じっこうじこすと・・・

はい、そういうわけで、ジェネリック数値処理ライブラリーは素直に C++ で書いて C# からは P/Invoke というか、P/Invoke 含めて C# でそういうトコは"無茶"して実装しないで、適当なプロセス間通信で適材適所に実装わけた設計にしてプログラマーも実行する電算機さんも楽したらいいんだろうね、って思いました。(´・ω・`)

ちなみに、記事の途中で触れたように、こういうジェネリックの要求がある場合に stackoverflow のイケメン C#er たちは「対応したい型ごと非ジェネリックできっちり書け(▲▲▲▲▲▲▲▲←賛同するイケメンC#erたちのアップヴォートのグレートな山脈が隆起)」みたいになっている様子だったし、わたしもこのC#の仕様の上でもやりたいのなら、それはまあ、 generics は無理しない程度の共通化に使いつつ型ごと実装かかんとちょっとしたループにハマったりするだけで実行時コストが爆発しちゃうだろうなーって思います。でもま、実行時コストが爆発しないところは少々無茶な実装だって、プログラマー=サンのライフがえぐられない方が総合的にみんなハッピーな事も、あるんじゃないかなー、とは思います。

おわり😂

C#, LINQ, Lambda-expression: LINQ の query-expression の中の select 句で lambda-expression を吐かせたい。でも C# ちゃんが翻訳死しちゃったの、ってスタック・オーバーフロー・チャンスしたら .NET イケメンの Reza Aqhaei が助けてくれたメモ。

// Example-0; 突然の翻訳死が訪れるよ
var xs = from v in Enumerable.Range( 0, 4 ) select w => w;

こういう事をしたいけど、これじゃ C# コンパイラーちゃんが死んじゃう😅

error CS1942: An expression type in select' clause is incorrect. Type inference failed in the call toSelect'

とりあえずラムダじゃなかったらキャプれるよね、というわけで、

// Example-1; こうすれば動くんだけどー
Func< int, int > f = w => w;
var xs = from v in Enumerable.Range( 0, 4 ) select ( v, f );

事前に定義して変数に束縛しておいたラムダと値のタプルを吐くってのはまあ、すぐに思い付いた妥協策だったんだ。

なんで「値とファンクターのタプルなんかを吐かせたの?」って??

そりゃぁ、だって、ぁぁーた、

// Example-2; 最初の例よろしくコレはダメなのよ
var xs = from v in Enumerable.Range( 0, 4 ) select w => w + v;

こういう事をしたかったからじゃないのさ😂 涙ぐましいタプルだったってわけ。

それで、「あーもう閃の軌跡したいんじゃーーーー(でもオレオレライブラリーの実装途中の機能が終わらないのもいやなんじゃあーーー」ってわけで、他力本願。

(お悩みを)必殺、スタック・オーバーフロー・チャーンス!

そしたら .NET イケメンの Reza Aghaei がササッと Answer してくれた😃

// Reza Aqhaei の教えてくれた Anser 
var xs = from v in Enumerable.Range(0, 4) select new Func<int, int>(x => x + v);

ぉーぃぇぁ、なるほどだよ・w・

lamda の直接キャプチャーが叶わないが、 Func<int,int> はキャプチャーできたところで気づけなかったのはちょっと悔しい。ありがとう、 Reza Aqhaei 🍣

C#: null れる class と null れない struct いずれの可能性もある object を as<T> したい場合の限界、あるいは C# らしい結論

// これはコンパイルできない(´・ω・`)
// object -> T
static public T As<T>( object o )
{ return o as T; }
// as T できるためには T に class 制約が必要。これはコンパイルできるし、期待動作するかもしれない。
// object(class=nullれる何か) -> T
static public T As<T>( object o )
  where T: class
{ return o as T; }
// これは As<T> が期待動作する。
object a = "Я(やー)";
var aa = As<string>( a );
Console.Write( aa );
// これは As<T> をコンパイルできないから期待動作できない。
object b = 4649;
var bb = As<int>( b ); // <-- oops!
Console.Write( bb );
// これはコンパイルできない(´・ω・`)
static public T? AsNullable<T>( object o )
{ return o as T?; }
// as T? できるためには T に struct 制約が必要。これはコンパイルできるし、期待動作するかもしれない。
static public T? AsNullable<T>( object o )
  where T: struct
{ return o as T?; }
object b = 4649;
var bb = AsNullable<int>( b ); // int? 型になる。
// 暗黙的に int 型のフリが得意な struct をラップした class 的に振る舞う。
Console.WriteLine( $"bb={bb} (bb==null)={bb==null}" );
// null れる
bb = null;
Console.WriteLine( $"bb={bb} (bb==null)={bb==null}" );
// でも(´・ω・`)…
int bbb = bb; // <-- oops! int? は int になれないのでコンパイルエラー。
// 「int? -> int または無かった時の int な補助要員を用意」ならできる
int bbb = bb ?? -1;
// つまり…(´・ω・`) これなら object から int とか struct な T へも As<T> できる。一応。
static public T As<T>( object o, T default_value )
  where T : struct
{ return o as T? ?? default_value; }
// 一応、期待動作する。
object b = 4649;
int bbb = As( b, -1 ); // 型パラメーターは第2実引数から推論できるので省略させられる
// つまり… 2つオーバーロードしておいて…
// 対 class 用 As<T>
static public T As<T>( object o ) where T : class { return o as T; }
// 対 struct 用 As<T>
static public T As<T>( object o, T default_value ) where T : struct { return o as T? ?? default_value; }
// int, float, double, string; string は class, そのほかは struct
var os = new object[]{ 123, 1.23, 12.3f, "hoge" };
// 一応、 どちらにも As<T> できないこともない…(´・ω・`)
foreach ( var o in os )
  Console.WriteLine( $"{As( o, -1 )}, {As( o, -2.0 )}, {As( o, -3.0f )}, {As<string>( o )}" );
// こんなんが出る。期待動作といえる場合もあるけど、言えない場合もある(´・ω・`)
// 型判定が厳密過ぎて As というより Is Then って感じだね(´・ω・`)
123, -2, -3, 
-1, 1.23, -3, 
-1, -2, 12.3, 
-1, -2, -3, hoge

で、この As<T>As<T> を使う実装者が object の中身が classstruct か知っていないと使えない。そういうわけで「一応」が抜けない(´・ω・`)

// これはダメ、コンパイルエラー。C#のメソッドのシグニチャーに where 句は含まれないので同じシグニチャーのメソッドの重複定義。
static public T As<T>( object o, T default_value ) where T : class { return o as T ?? default_value; }
static public T As<T>( object o, T default_value ) where T : struct { return o as T? ?? default_value; }
// これは期待動作しちゃう。シグニチャーは異なる事になってコンパイルできるし、さっきの os と As<T> の使い方なら期待動作する。
static public T As<T>( object o, object default_value = null ) where T : class { return o as T ?? default_value as T; }
static public T As<T>( object o, T default_value ) where T : struct { return o as T? ?? default_value; }
// でも、こうすると死ぬので「期待動作しちゃう」だった。
var os = new object[]{ 123, 1.23, 12.3f, "hoge" };
foreach ( var o in os )
  Console.WriteLine( $"{As( o, -1 )}, {As( o, -2.0 )}, {As( o, -3.0f )}, {As( o, "fuga" )}" ); // <-- 最後の As も書き方を統一してみちゃうと死ぬ。

As<T>( o, "fuga" )class 制約の方が呼ばれて欲しいんだけど、そんなプログラマーのお気持ちは言語仕様は組んでゆるふわ動作してくれないから、死ぬ。

// あーれこれと考えた結果、たどり着いた C# の限界に無理をしない As<T> たち
static public T AsClass<T>( object o ) where T : class { return o as T; }
static public T? AsStruct<T>( object o ) where T : struct { return o as T?; }
// ついでにおまけ、(double)34.56 を (int)35 に As りたいとかそーゆー場合用
static public T AsConvertible<T>( object o ) where T : IConvertible { return (T)Convert.ChangeType( o, typeof(T) ); }

まあ…、一応考えてはみましたよ、内部的には AsClass<T>AsStruct<T> も試した結果を (TC,TS?) なタプルで返して…とか、 class X<TC,TS> な感じで classobjectstruct?object をプロパティーで保持しつつ、 operator 群, Equals, GetHashCode, GetType をよしなに実装した薄いラッパーを As<T> で返させて…とか、いっそ実引数で型パラメーター相当の…とか。

でも、 C# の言語仕様の限界をそんな疲れて保守性も悪そうな何かを必死に作ってどうにかせんでも…と思い、そもそもそうなると↑の AsClass, AsStruct, AsConvertible も何かの部品に internal で使うわけでなければ、ほとんど存在価値がないどころか無駄なわけで…。

// 実行時に謎の object をあれかこれかそれか…って処理したいなら(´・ω・`)
void f( object o )
{
  switch ( o )
  {
    case IConvertible convertible: // ... 略
    case MyStructX sx: // ... 略
    case MyClass1 c1: // ... 略
    case MyClass2 c2: // ... 略
  }
}

メソッドでディスパッチしたり包括的に処理するかっこいい何かとかを考えたりするよりも、実際問題、これが一番書くのも保守するのもらく😂

C++er には C#generics の貧弱さが…ツライ😅