Prism7.2 バインディング時に有効なアクセス修飾子

4 min read読了の目安(約4400字

環境

C# : 9.0
Prism : 7.2
Xamarin.Forms : 4.4
.NET Standard 2.1

懺悔 21/01/18

冷静になって考えてみるとこの記事はPrismの問題でも、Xamarinの問題でもありませんでした...しいて言えばC#の言語仕様を自分が忘れていたことが問題でした。
大変失礼いたしました。

ViewModelで宣言したprotectedなメンバは他のクラスから見えるわけがないので、たとえXAMLからだろうがバインド指定していようが、アクセスはできないです。
厳密には絶対にアクセスができないわけではないですが、そんなことをわざわざPrismやXamarinが対応しているはずもなく、というか対応する意味もなく...

結論としてはとりあえずバインドするViewModelのメンバはpublicで宣言しておきましょうということです。

記事本文は自分の戒めとして残しておきます。
(せめてBind失敗云々のエラーを出してくれたら...というのは甘えですかね...)

Prismのバインディング

Prismでバインディングをするときは、まずPageとViewModelをDIコンテナに登録します。
そして、BindableBaseを継承したViewModel側でメンバを作成して、PageのXAMLで
{Binding メンバ}と書いて、バインドします。
例)

App.xaml.cs
public partial class App
	{
		// 諸々省略
		protected override void RegisterTypes(IContainerRegistry containerRegistry)
		{
			containerRegistry.RegisterForNavigation<NavigationPage>();
			containerRegistry.RegisterForNavigation<LoginPage, LoginPageViewModel>();
		}
	}
LoginPageViewModel.cs
public class LoginPageViewModel() : BindableBase
{
	private string title;
	public string Title
	{
		get => title;
		set => SetProperty(ref title, value);
	}
	
	public LoginPageViewModel(INavigationService navigationService)
	{
		Title = "ログイン"
	}
	// 諸々省略
}
LoginPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DeliveryInspection.Views.LoginPage"
             Title="{Binding Title}">
    
    <ContentPage.Content>
        <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
		<Label Text={Binding Text} />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>
xmlns:prism="http://prismlibrary.com"
prism:ViewModelLocator.AutowireViewModel="True"

Prism6以降だとこれは必要ないようです。
登録したら自動でやってくれるみたいです。

これでLoginPageを開くとLoginPageのNavigationBarにログインと表示されます。
非常に簡単にできてしまいました。

本題

ここからが本題です。
私は今開発しているプロジェクトでViewModelBaseを作り、ViewModelには全てにViewModelBaseを継承させています。
ViewModelBaseは以下のようなコードになっています。

ViewModelBase.cs
public class ViewModelBase : BindableBase, IInitialize, INavigationAware, IDestructible
	{
		protected INavigationService NavigationService { get; private set; }

		private string title;
		protected string Title
		{
			get => title;
			set => SetProperty(ref title, value);
		}

		protected ViewModelBase(INavigationService navigationService)
		{
			NavigationService = navigationService;
		}
		
		public virtual void Initialize(INavigationParameters parameters)
		{
		}

		public virtual void OnNavigatedFrom(INavigationParameters parameters)
		{
		}

		public virtual void OnNavigatedTo(INavigationParameters parameters)
		{
		}

		public virtual void Destroy()
		{
		}
	}

これをさっきのLoginPageViewModelに継承させ、同じようにTitleにログインと表示させようとしました。
しかし、何をやっても、何を見直しても一向にタイトルの文字は現れないのです。
試しにボタンのCommandバインディングはどうかと試してみたところ、正常に動いてくれました。
CommandはICommandで宣言してバインドです。

LoginViewModel.cs
public class LoginPageViewModel : ViewModelBase
	{
		public ICommand GoToTabbedPageCommand { get; }

		public LoginPageViewModel(INavigationService navigationService)
			: base(navigationService)
		{
			Title = "ログイン";
			GoToTabbedPageCommand = new DelegateCommand(GoNextPageAsync);
		}

		private async void GoNextPageAsync()
		{
			await NavigationService.NavigateAsync($"/{nameof(TopPage)}");
		}
	}
LoginPage.xaml
<!-- 諸々省略 -->
<ContentPage.Content>
        <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
            <Button Text="ログイン"
                    Command="{Binding GoToTabbedPageCommand}"/>
        </StackLayout>
</ContentPage.Content>

TopPageは省略しますが、これでNavigationStackにTopPageがスタックされ、画面遷移します。

わかっていらっしゃる方はおそらくわかるのだと思うのですが、私は罠にはまりました。
もしかしたらPrism常用者の方には当たり前のことで罠と言っていいのかはわかりませんが...

どうやらPrismのBindingではprotectedなメンバは対応してなかったようなのです...

つまり、ViewModelBaseは以下のように直さないといけません。

ViewModelBase.cs
public class ViewModelBase : BindableBase, IInitialize, INavigationAware, IDestructible
	{
		// 諸々省略
		private string title;
		// protectedではなくpublic
		public string Title
		{
			get => title;
			set => SetProperty(ref title, value);
		}
		
		//protected string Title
		//{
		//	get => title;
		//	set => SetProperty(ref title, value);
		//}
	}

こうすることで無事にバインドが働きました。
かなり時間を無駄にしてしまいました。
初めからpublicにしていればいいものを、Riderさんからprotectedにしたほうがいいよと言われ、安易にそれに乗っかったことが今回の敗因です。

これからPrismを使用される方は気を付けてください。
(Xamarin.Formsでも同じなのかな)
もしpublicなメンバ以外をバインドする方法もあるぜ!という情報がありましたら、教えていただけると嬉しいです...

以上、Prism初心者のPrismで気を付けることでした。