Lambda式が使える汎用ValueConverterを作ったが、いまいちだった話


WPFで値をBindingする際、そのままの値を使うのではなく
定数を加算、2倍、逆数など 簡単な演算をしたい事が度々あります。
毎回それ用にプロパティを用意したり、専用ValueConverterを作るのも面倒なので
Lambda式で処理を渡せる汎用のValueConverterを作ってみました。

こちらに同じような記事があったのですが
Converterを書くのが面倒だというはなし
IronPythonか~と思い、自分でも作ることに。

結果としては上手くいかず、今回は専用のValueConverterを作ることにしたのですが、
使える場面もあるかなと思い、ここに残す事にしました。

上手く行かなかった事

Microsoft.CodeAnalysis.CSharp.Scriptingを使ってるのですが
EvaluateAsyncの初回起動が遅いためか、アプリそのものの起動時間がかかるようになってしまいました
(今回のケースでは起動時からTextBoxに初期値が入っていてConverterが走ります)。
Bindingの際にIsAsync=Trueとすると起動時間は短縮できるものの、
FallbackValueには固定値しか設定できないため、値が変わる度にちらついてしまいます。
Converter完了まで前回の値を表示しておく事ができれば良いのですが…

実装

Lambda式をConverterParameterで文字列として受け取り
Microsoft.CodeAnalysis.CSharp.Scriptingで処理しています。

Converter.cs
public class LambdaConverter : IValueConverter
{
    public Object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var func = CSharpScript.EvaluateAsync<Func<dynamic, dynamic>>($"(Func<dynamic, dynamic>)({parameter as String})",
            ScriptOptions.Default
            .WithImports("System")
            .WithReferences(typeof(Microsoft.CSharp.RuntimeBinder.Binder).Assembly)
            ).Result;
        return func(value);
    }

    public Object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

ConverterParameterで処理内容をLambda式で定義します。

View.xaml
<TextBox x:Name="InputData" Width="100"
         Text="{Binding InputData, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, FallbackValue=0}"/>
<TextBlock Margin="10,0"
           Text="{Binding InputData, Converter={StaticResource LambdaConverter}, ConverterParameter='e => e + 1'}"/>
 <TextBlock
           Text="{Binding InputData, Converter={StaticResource LambdaConverter}, ConverterParameter='e => 1 / e'}"/>

こんな感じ

起動さえすれば、実行中の速度は気にならないんですけどね~