8558


次のような関数を作ろうとしています。

[code]

    public static void Hoge<int>(Func<int> function)
    {
        var value = function();
        var expression = new Expression<Func<int>>(function);  // ←ビルドエラー。何か方法は無いか？
        var name = (expression.Body as MemberExpression).Member.Name;
    
        // 以下、valueとnameを使った処理
    }
    
[/code]

`Hoge(() => x)` という感じに呼び出して、変数名`x`とその値を同時に渡したいのです。  
しかし、このような`Expression<T>`のコンストラクタは存在しないので、このままでは実現できません。  
`Hoge()`の引数を`Expression<Func<int>>`にして、`Compile()`してから実行して値を取り出す、というのも可能ですが、`Compile()`がとても遅いのでやりたくないです。  
※`MemberExpression`経由で変数名を取り出すのに比べて2桁以上遅い

↓こういう形ならさくっと`Expression`インスタンスを作れるから簡単だろうと思いましたが、もしかしてこれはコンパイル時にすごいことをやってくれているだけなのであって、実行時にやろうとすると容易ではないのでしょうか？

[code]

    int x = 10;
    Expression<Func<int>> exp = () => x;
    
[/code]


CC BY-SA 3.0
2015-03-30T05:01:22.733
8553
2015-03-30T23:42:13.143
2015-03-30T07:40:23.417
8000
8078
question
c#
Func<T> から Expression<Func<T>> のインスタンスを生成する方法


おそらく、無いと思います。残念ですが…。  
代替案ですが、プロパティみたいに何回も同じものを渡す場合は、引数を`Expression<Func<T>>`にしておいて、内部で`Compile`した結果を`Dictionary<string,
Func<T>>`みたいなものにキャッシュしておくと2回目からは早くできます。


CC BY-SA 3.0
2015-03-30T06:25:53.047
8554
2015-03-30T07:17:23.853
2015-03-30T07:17:23.853
4765
9063
8553
answer


`Func<T>` は実行可能なコードへの参照、`Expression` は構文木のようなデータ構造ですから、`Func<T> → Expression`
の変換は逆コンパイルをするようなものです。

`Expression<T> e = ラムダ式;` は「`Expression`
を生成するプログラム」に変換される糖衣構文のようなものですが、コンパイル中には当然その部分の構文木もあるわけなので、これを `Expression`
に変形するのも容易でしょう。

ラムダ式をデリゲートと式木（あるいは文字列）の両方にコンパイルできると便利そうですが、実装される気はしないですね・・・。

[code]

    // 妄想：後から変換できるように
    Lambda<Func<int>> l = () => 1; //この型はコンパイル時のみ存在する？？
    Func<int> f = l;
    Expression<Func<int>> e = l; //式木にできないラムダ式はどうする？
    
    // 妄想：同時に受け取る
    Func<int> f, Expression<Func<int>> e;
    (f, e) = () => 1; //この構文はC#にはない
    
[/code]

参考

  * [c# - converting a .net `Func<T>` to a .net `Expression<Func<T>>` - Stack Overflow](https://stackoverflow.com/q/767733/2818869)
  * [式木（Expression Trees） (C# によるプログラミング入門)](http://ufcpp.net/study/csharp/sp3_expression.html)
  * [Expression(TDelegate) クラス (System.Linq.Expressions)](https://msdn.microsoft.com/ja-jp/library/bb335710.aspx)

* * *

追記：質問にあるコードでは `() => hoge.piyo`
といった式を期待されているようですが、単純なフィールド・プロパティアクセスならリフレクションを使うと速いかもしれません。

[c# - How do I get the value of MemberInfo? - Stack
Overflow](https://stackoverflow.com/q/238555/2818869)


CC BY-SA 3.0
2015-03-30T08:32:27.807
8558
2015-03-30T08:59:49.803
2017-05-23T12:38:56.467
-1
8000
8553
answer


質問の状況であれば逆コンパイルによって`Func<int>`から変数名を読み取ることは可能です。

考え方としては

  1. `Delegate`インスタンスの`Method`プロパティから`MethodInfo`を取得する
  2. `MethodInfo`から`GetMethodBody().GetILAsByteArray()`でILコードを取得する
  3. IL中の`ldfld`(`0x7B`)がフィールドから値を読みだしている箇所で、続く4バイト(リトルエンディアン)が`FieldInfo.MetadataToken`に該当する
  4. フィールドの定義されている`ModuleHandle`は`Delegate.Target`→`Object.GetType()`→`Type.TypeHandle`→`RuntimeTypeHandle.GetModuleHandle()`で取得できる
  5. `ModuleHandle.ResolveFieldHandle(int)`でメタデータトークンからハンドルが取得できる
  6. `FieldInfo.GetFieldFromHandle()`で`FieldInfo`が取得できる

のようになります。

[code]

    static class Program
    {
        static void Main(string[] args)
        {
            var a = typeof(Program).GetHashCode();
            var b = typeof(Program).GetType().GetHashCode();
            WriteFieldName(() => a);
            WriteFieldName(() => b);
            Console.ReadKey();
        }
        private static void WriteFieldName(Func<int> a)
        {
            var body = a.Method.GetMethodBody().GetILAsByteArray();
            var ldfldPos = Array.LastIndexOf(body, (byte)0x7B);
            var metadataToken = (body[ldfldPos + 4] << 24) + (body[ldfldPos + 3] << 16) + (body[ldfldPos + 2] << 8) + body[ldfldPos + 1];
            Console.WriteLine(FieldInfo.GetFieldFromHandle(a.Target.GetType().TypeHandle.GetModuleHandle().ResolveFieldHandle(metadataToken)).Name);
        }
    }
    
[/code]


CC BY-SA 3.0
2015-03-30T11:43:15.920
8568
2015-03-30T11:43:15.920
5750
8553
answer


この処理の目的ってなんでしょうか？ 処理速度が求められるものなのでしょうか？

別のアプローチとしてC# 6.0になるとnameof演算子が導入されるので変数名を渡すのも楽になります。

[code]

    // Hoge(x, "x") が呼び出される
    Hoge(x, nameof(x));
    
[/code]

もしくはC# 3.0の匿名型＋拡張メソッドで対処できる場合もあります。

[code]

    static void Hoge(this object obj) {
        var type = obj.GetType();
        //
    }
    
    // プロパティxに変数xの値が格納された匿名型オブジェクト
    new{ x }.Hoge();
    
[/code]


CC BY-SA 3.0
2015-03-30T23:42:13.143
8582
2015-03-30T23:42:13.143
4236
8553
answer