F# でリフレクションを駆使して WinRT を扱ったコードを C# と比較します。
この記事のコードは以下のリポジトリに掲載しています。
C# のコードは以下の記事から引用します。実行結果はこちらを参照してください。
将来展望
現在開発中の C#/WinRT と F# 5.0 を組み合わせれば、リフレクションを駆使した泥臭いコードは不要になる見込みです。
概要
WinRT は通常の .NET アセンブリとは異なるため言語側でもサポートが必要です。記事執筆時点で F# には WinRT のサポートがないため、リフレクション経由で扱う必要があります。
特に WinRT で多用される非同期処理が問題になります。詳細は以下の記事を参照してください。
音声一覧
読み上げに使用できる音声一覧を取得します。
using System;
using Windows.Media.SpeechSynthesis;
class Program
{
static void Main()
{
foreach (var voice in SpeechSynthesizer.AllVoices) {
Console.WriteLine("{0}: {1}", voice.Language, voice.Description);
}
}
}
open System
let SpeechSynthesizer = Type.GetType(@"
Windows.Media.SpeechSynthesis.SpeechSynthesizer,
Windows.Foundation.UniversalApiContract,
ContentType=WindowsRuntime")
for voice in SpeechSynthesizer.GetProperty("AllVoices").GetValue(null) :?> seq<obj> do
let t = voice.GetType()
let lang = t.GetProperty("Language" ).GetValue(voice) :?> String
let desc = t.GetProperty("Description").GetValue(voice) :?> String
printfn "%s: %s" lang desc
インスタンスは obj
のまま扱って、メンバーはリフレクション経由で触ります。かなり面倒です。
音声合成
音声を指定してしゃべらせます。
using System;
using System.Threading.Tasks;
using Windows.Media.Core;
using Windows.Media.SpeechSynthesis;
using Windows.Media.Playback;
class Program
{
static void SetVoice(SpeechSynthesizer synthesizer, string v) {
foreach (var voice in SpeechSynthesizer.AllVoices) {
if (voice.DisplayName == v) {
synthesizer.Voice = voice;
break;
}
}
}
static async Task Main()
{
var voice = "Microsoft Ichiro";
var text = "こんにちは、世界";
var synthesizer = new SpeechSynthesizer();
SetVoice(synthesizer, voice);
var stream = await synthesizer.SynthesizeTextToStreamAsync(text);
var player = new MediaPlayer();
player.Source = MediaSource.CreateFromStream(stream, stream.ContentType);
var tcs = new TaskCompletionSource<int>();
player.MediaEnded += (sender, o) => tcs.SetResult(0);
player.Play();
await tcs.Task;
}
}
#r "System.Runtime.WindowsRuntime"
open System
open System.Threading.Tasks
let SpeechSynthesizer = Type.GetType @"
Windows.Media.SpeechSynthesis.SpeechSynthesizer,
Windows.Foundation.UniversalApiContract,
ContentType=WindowsRuntime"
let SpeechSynthesisStream = Type.GetType @"
Windows.Media.SpeechSynthesis.SpeechSynthesisStream,
Windows.Foundation.UniversalApiContract,
ContentType=WindowsRuntime"
let MediaPlayer = Type.GetType @"
Windows.Media.Playback.MediaPlayer,
Windows.Foundation.UniversalApiContract,
ContentType=WindowsRuntime"
let TypedEventHandler(a, b) = Type.GetType(@"
Windows.Foundation.TypedEventHandler`2,
Windows.Foundation.FoundationContract,
ContentType=WindowsRuntime").MakeGenericType(a, b)
let AsTaskGeneric = query {
for m in typeof<WindowsRuntimeSystemExtensions>.GetMethods() do
where (m.Name = "AsTask")
let ps = m.GetParameters()
where (ps.Length = 1 && ps.[0].ParameterType.Name = "IAsyncOperation`1")
select m
exactlyOne }
let await resultType winRtTask =
let AsTask = AsTaskGeneric.MakeGenericMethod [|resultType|]
let task = AsTask.Invoke(null, [|winRtTask|])
task.GetType().GetProperty("Result").GetValue(task)
let setVoice synthesizer (v: string) =
let AllVoices = SpeechSynthesizer.GetProperty("AllVoices")
match [ for voice in AllVoices.GetValue(null) :?> seq<obj> do
let DisplayName = voice.GetType().GetProperty("DisplayName")
if (DisplayName.GetValue(voice) :?> String) = v then yield voice ] with
| [voice] -> synthesizer.GetType().GetProperty("Voice").SetValue(synthesizer, voice)
| _ -> ()
let voice = "Microsoft Ichiro"
let text = "こんにちは、世界"
let synthesizer = Activator.CreateInstance(SpeechSynthesizer)
setVoice synthesizer voice
let stream =
SpeechSynthesizer.GetMethod("SynthesizeTextToStreamAsync").Invoke(synthesizer, [|text|])
|> await SpeechSynthesisStream
let player = Activator.CreateInstance(MediaPlayer)
MediaPlayer.GetMethod("SetStreamSource").Invoke(player, [|stream|])
let tcs = TaskCompletionSource<unit>()
type C = static member f(_: obj, _: obj) = tcs.SetResult()
let d = Delegate.CreateDelegate(TypedEventHandler(MediaPlayer, typeof<obj>), typeof<C>.GetMethod("f"))
MediaPlayer.GetEvent("MediaEnded").AddMethod.Invoke(player, [|d|])
MediaPlayer.GetMethod("Play").Invoke(player, [||])
tcs.Task.Result
かなり厳しい感じです。この調子で複雑なものを作るのは、あまり現実的ではない気がします。
イベントハンドラ
型を指定してデリゲートを作るには MethodInfo
が必要です。F# の関数から MethodInfo
を取得しようとすると、IL を解析するようなハックが必要になるようです。
そこまでしなくてもクラスを定義すれば済むため、メソッドが1つだけのクラスを作ります。
type C = static member f(_: obj, _: obj) = tcs.SetResult()
let d = Delegate.CreateDelegate(TypedEventHandler(MediaPlayer, typeof<obj>), typeof<C>.GetMethod("f"))
TypedEventHandler は2つの引数を持つジェネリック デリゲートです。
public delegate void TypedEventHandler<TSender,TResult>(TSender sender, TResult args);
TypedEventHandler<MediaPlayer, Object>
と C.f
では第1引数の型が異なりますが、引数の反変性として容認されます。
感想
いずれ不要になることも分かっているので、あまり頑張っても仕方ない気はしますが、リフレクションで動的に型を扱う練習にはなると思いました。(WinRT でなくてもできますが)