INotifyPropertyChanged.ObservePropertyの引数に指定できるのは選ばれし者だけという話

# ObservePropertyでArgumentException！？
MVVMで以下のようなコードをModelとViewModelに書いていました。なお、MVVMフレームワークにPrismを使用しています。Modelくんは可視状態を`bool`型で持ち、ViewModelくんはそれを`IObservable<bool>`に変換してから`ToReadOnlyReactiveProperty`でバインドするプロパティに変えます。

```c#:Model.cs
public class MyModel : BindableBase
{
    // PG内部で持ち回る可視状態(PrismのBindableBaseでINotifyPropertyChangedを実装)
    public bool IsVisible { get => _isVisible; set => SetProperty(ref _isVisible, value); }
    private bool _isVisible = false;

    public void なんかモデル的なメソッド() { IsVisible = !IsVisible; }
}

```
```c#:ViewModel.cs
public class ViewModel
{
    private readonly MyModel _model;
    // 画面にバインドする可視状態
    public ReadOnlyReactiveProperty<Visibility> VisibilityKind { get; }
    public ViewModel(MyModel model)
    {
        _model = model;
        // BooleanConverterだとCollapsedになってヤなので自分でHiddenに設定する
        VisiblityKind = _model.ObserveProperty(m => m.IsVisible ? 
            Visibility.Visible : Visibility.Hidden
        ).ToReadOnlyReactiveProperty();
    }
}
```
VMのソースはコンパイルできますが、`ObserveProperty`の行を通ると`ArgumentException`が起きます。
アイエエエ！`ArgumentException`！？`ArgumentException`ナンデ！？

# 例外をスローしている箇所
C#の構文的には合っているのに実行時にエラーになるということは、`ObserveProperty`が`ArgumentException`をスローしているに違いありません（というか`ArgumentException`は`ぬるり`みたいなのと違って開発者が`throw`しないと来ないし）。
というわけで`ObserveProperty`の内部実装を見てみましょう。この神メソッドはGod of Godsプロジェクトである[ReactiveProperty](https://github.com/runceel/ReactiveProperty)の[INotifyPropertyChangedExtensions.cs](https://github.com/runceel/ReactiveProperty/blob/d1e1f9e788332340f15f6eb9bb6186096c2336f3/Source/ReactiveProperty.NETStandard/Extensions/INotifyPropertyChangedExtensions.cs#L34)に実装されていらっしゃいます。以下、コードを引用します。インデントだけ調整させてもらいました。

```c#:INotifyPropertyChangedExtensions.cs
    public static IObservable<TProperty> ObserveProperty<TSubject, TProperty>(
        this TSubject subject, Expression<Func<TSubject, TProperty>> propertySelector,
        bool isPushCurrentValueAtFirst = true)
        where TSubject : INotifyPropertyChanged
    {
        return ExpressionTreeUtils.IsNestedPropertyPath(propertySelector) ?
            ObserveNestedProperty(subject, propertySelector, isPushCurrentValueAtFirst) :
            ObserveSimpleProperty(subject, propertySelector, isPushCurrentValueAtFirst);
    }
```
`ObserveProperty`自体は例外をスローしていません。ということは内部で呼んでいるメソッドが怪しいです。上から順に見ていきましょう。まずは[ExpressionTreeUtils.cs](https://github.com/runceel/ReactiveProperty/blob/main/Source/ReactiveProperty.Core/Internals/ExpressionTreeUtils.cs)の[IsNestedPropertyPathメソッド](
https://github.com/runceel/ReactiveProperty/blob/d1e1f9e788332340f15f6eb9bb6186096c2336f3/Source/ReactiveProperty.Core/Internals/ExpressionTreeUtils.cs#L42)です。

```c#:ExpressionTreeUtils.cs
    public static bool IsNestedPropertyPath<TSubject, TProperty>(Expression<Func<TSubject, TProperty>> propertySelector)
    {
        if (propertySelector.Body is MemberExpression member)
        {
            return !(member.Expression is ParameterExpression);
        };

        if (propertySelector.Body is UnaryExpression unary)
        {
            if (unary.Operand is MemberExpression unaryMember)
            {
                return !(unaryMember.Expression is ParameterExpression);
            }
        }

        throw new ArgumentException();
    }
```
**`ArgumentException`投げてるじゃん！**というわけで元のコードが特定できたので、コードを読んで原因を調べていきます。
# 原因
このコードでは引数となる`Expression<Func<TSubject, TProperty>>`型の`propertySelector`を調べて、条件に該当しなければ例外をスローしているようです。そもそも`Expression<Func<T>>`ってなんやねん？というと、**ラムダ式(の式木)**を示す型のようです。つまり、`ObserveProperty`メソッドの引数に渡されたラムダ式の構造をチェックしているというわけですね。詳細はわからなくても、**引数の本体が`MemberExpression`か`UnaryExpression`であるラムダ式でないとダメ**なようです。Memberということはおそらくメンバ名をそのまま指定しないといけないのでしょう。Unaryはなんなんだろう？このあたりの想像は[SouceGeneratorを作るためにRoslynコンパイラを触って構文解析した経験(宣伝)](https://qiita.com/ry18847/items/15c979b67e0372c22f1d)が生きます。
ここで、エラーになる行のコードをもう一度見てみます。

```c#:ViewModel.cs
        // BooleanConverterだとCollapsedになってヤなので自分でHiddenに設定する
        VisiblityKind = _model.ObserveProperty(m => m.IsVisible ? 
            Visibility.Visible : Visibility.Hidden
        ).ToReadOnlyReactiveProperty();
```

ぜんぜん`MemberExpression`っぽくないですね。普通に式を書いてしまっています。
そもそも、`IObservable<T>`の内容を変えて`ReactiveProperty`にしたかったら`ReactiveExtensions`で

```c#
_model.ObserveProperty(m => m.IsVisible). //IObservable<bool>を
       Select(b => b ? Visibility.Visible : Visibility.Hidden). //IObservable<Visibility>にして
       ToReadOnlyReactiveProperty(); // ReactivePropertyにする
```
こんなふうに書かないといけないのでした。これで`ArgumentException`は起きません。めでたしめでたし
# まとめ
絶対神`ObserveProperty`メソッドを使うには`IObservable<T>`にしたいメンバをそのまま指定して、その値に対して絞り込みなり編集なりしたければ`ReactiveExtensions`で`Linq`チックに実装をしましょう。`ObserveProperty`メソッドに謁見できる存在は`MemberExpression`か`UnaryExpression`という選ばれし存在のみだからです。
最近のC#やNugetで取得できるライブラリはオープンソースばかりで、Githubにコードが公開されているのでいざとなったら直接中身を見て原因究明できるのがいいですね～！