あるオブジェクトのプロパティ値を別のオブジェクトのプロパティ値にコピーするコードは、よく書くものだ。特にMVVMなどのアーキテクチャでロジックと画面を分離するときには、頻繁に記述することになる。そのようなコードの記述は退屈だし、それ故、間違いも入り込みやすい。自動化できたらよいのにと思ったことはないだろうか? それを実現する「AutoMapper」というオープンソースのライブラリがある。本稿では、AutoMapperの基本的な使い方を紹介する。
なお、本稿執筆時点でAutoMapperがサポートしているのは.NET Framework 4以降であるが、NuGetから導入するため、本稿ではVisual Studio 2012を使って説明する*1。また、本稿のサンプルは「MSDN Code Recipe:.NET Tips #1102」からダウンロードできる。
Visual StudioのプロジェクトごとにNuGetから導入する(次の画像)。
AutoMapperの基本的な機能は、二つのクラスの間で同じ名前のプロパティを見つけ出し、それらのプロパティ値をコピーすることである。以下、例題を示した上で、基本的な使い方を説明しよう。
ここでは例題として、人の情報を格納する「Person」クラスと「PersonForDisplay」クラスという二つのクラスを考えてみよう。
「Person」クラスはロジックで使うオブジェクトだとする。ロジックで使うオブジェクトは、シンプルなものにしたい。画面の都合に振り回されたくないのである。例えば次のコードのようになる。
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public static IList<Person> GetData()
Public Class Person
Public Property FirstName As String
Public Shared Function GetData() As IList(Of Person)
「FirstName」と「LastName」という二つのプロパティを持つシンプルなクラスである。
また、何らかの方法でデータを取得してくる「GetData」メソッドがある(このクラスに配置することには異論があるかもしれないが、サンプルということでご容赦願いたい)。
なお、このVBのコードでは、Visual Basic 2005からの機能である
ジェネリック型や、Visual Basic 2008から利用できるようになった
オブジェクト初期化子、そしてVisual Basic 2010から利用できるようになった
自動実装プロパティを使用している。
次に、「PersonForDisplay」クラスは、WPFの画面にバインドして表示するためのものだとする。画面には、「FirstName」と「LastName」ではなく、空白を挟んで両方のプロパティ値をつなげた「DisplayName」プロパティを表示したいとしよう。また、データの変更を画面に反映させたいので、INotifyPropertyChangedインターフェース(System.ComponentModel名前空間)も実装したいものとする。すると、次のコードのようにちょっと複雑なクラスになる。
public class PersonForDisplay : System.ComponentModel.INotifyPropertyChanged
{
// 公開するイベントハンドラー(INotifyPropertyChangedインターフェースの実装)
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new System.ComponentModel.PropertyChangedEventArgs(info));
}
// FirstNameプロパティ
// LastNameプロパティ
// 画面に表示したいDisplayNameプロパティ
Public Class PersonForDisplay
Implements ComponentModel.INotifyPropertyChanged
' 公開するイベントハンドラー(INotifyPropertyChangedインターフェースの実装)
' FirstNameプロパティ
' LastNameプロパティ
' 画面に表示したいDisplayNameプロパティ
画面で使う「PersonForDisplay」クラスの例(上:C#、下:VB)
ロジックで使う先ほどの「Person」クラスに比べて、かなり長いコードになっている。画面に表示したい「DisplayName」プロパティと、データの変更を画面に反映させるためのINotifyPropertyChangedインターフェースを実装したためだ。
このような複雑なクラスはロジックで使いたくない。また、ロジックと関係のないSystem.ComponentModel名前空間がロジックに入ってくるのも避けたいものだ。そのため、このように「Person」と「PersonForDisplay」の二つのクラスに分けて設計を進めることになる。
また、画面には「PersonForDisplay」オブジェクトのリストを表示したいとする。デザイン画面での利便性を考えると、「PersonForDisplay」オブジェクトのコレクションを持つ「PersonsList」クラスも必要になる(次のコード)。
public class PersonsList
{
private System.Collections.ObjectModel.ObservableCollection<PersonForDisplay>
_persons = new System.Collections.ObjectModel.ObservableCollection<PersonForDisplay>
(new List<PersonForDisplay>());
public System.Collections.ObjectModel.ObservableCollection<PersonForDisplay>
Persons { get { return _persons; } }
public PersonsList()
Public Class PersonsList
Private _persons _
As System.Collections.ObjectModel.ObservableCollection(Of PersonForDisplay) _
= New System.Collections.ObjectModel.ObservableCollection(Of PersonForDisplay) _
(New List(Of PersonForDisplay)())
Public ReadOnly Property Persons _
As System.Collections.ObjectModel.ObservableCollection(Of PersonForDisplay)
Get
Return _persons
End Get
End Property
Public Sub New()
PersonForDisplayオブジェクトを格納するObservableCollectionオブジェクト(System.Collections.ObjectModel名前空間)を、プロパティとして公開している。
WPFのXAMLコードの側でPersonForDisplayオブジェクトのコレクションを生成してバインドするには、このようなクラスを用意しておくとよい。省略した部分でダミーデータを設定すれば、デザイン画面でもデータが表示されるようになる。
なお、コードビハインドでObservableCollectionオブジェクトを生成して画面のコントロールにバインドするのであれば、このクラスは不要だ。しかしそうすると、デザイン画面ではデータが表示されない。
なお、このVBのコードでは、Visual Basic 2005からの機能である
ジェネリック型を使用している。
この「PersonsList」クラスをWPFのListViewコントロール(System.Windows.Controls名前空間)にバインドして表示させる例を、次の画像に示す。
AutoMapperを使わない場合は、画面が表示されるときなどに、次のコードのようにしてプロパティ値を逐一コピーしなければならない。
// ロジックを呼び出してデータを取得する
IList<Person> data = Person.GetData();
// 表示用のコレクションを用意する
// データを表示用のコレクションにコピーする(AutoMapper未使用)
// 表示用のコレクションをデータコンテキストにセット
' ロジックを呼び出してデータを取得する
Dim data As IList(Of Person) = Person.GetData()
' 表示用のコレクションを用意する
' データを表示用のコレクションにコピーする(AutoMapper未使用)
' 表示用のコレクションをデータコンテキストにセット
これはWPFの画面のコードビハインドに記述する例である。ロジックを呼び出してデータを取得した後、それを表示用のコレクションに詰め替える処理を書く必要がある。ここではコピーするプロパティが二つだけだが、実際にはもっと多いだろう。
なお、このVBのコードでは、Visual Basic 2005からの機能である
ジェネリック型や、Visual Basic 2008から利用できるようになった
オブジェクト初期化子を使用している。
上のコードでは、同じ名前のプロパティ値をコピーするコードが何行も続く(サンプルなので2行しかないが、実際にはもっと多いだろう)。AutoMapperを使うことで、この部分を簡潔に書けるのだ。
先のコードで「データを表示用のコレクションにコピーする」というコメントを付けた部分は、AutoMapperを使うと次のコードのように簡潔に書ける。
// AutoMapperを使う(オブジェクトごと)
AutoMapper.Mapper.CreateMap<Person, PersonForDisplay>();
foreach (Person p in data)
personsList.Persons.Add(
AutoMapper.Mapper.Map<PersonForDisplay>(p)
);
' AutoMapperを使う(オブジェクトごと)
AutoMapper.Mapper.CreateMap(Of Person, PersonForDisplay)()
For Each p As Person In data
personsList.Persons.Add( _
AutoMapper.Mapper.Map(Of PersonForDisplay)(p))
Next
AutoMapperを使ってオブジェクトごとにデータをコピーする例(上:C#、下:VB)
従来の書き方ではプロパティの数だけ書いていたデータコピーのコードが、AutoMapperを使うと2行だけになる(CreateMapメソッド呼び出しとMapメソッド呼び出しの2行)。また、AutoMapperはコピー先のオブジェクト(ここではPersonForDisplayクラスのインスタンス)を生成してくれるので、コンストラクターの呼び出しも不要になっている。
AutoMapperを使うには、まずMapperクラス(AutoMapper名前空間)のCreateMapメソッドを呼び出して、コピー元とコピー先のクラスを登録する。型引数は、コピー元/コピー先の順だ。
実際にオブジェクトをコピーするには、MapperクラスのMapメソッドを使う。Mapメソッドの型引数はコピー先のクラスである。Mapメソッドの引数には、コピー元のオブジェクトを渡す。すると、Mapメソッドはコピー先のオブジェクトを生成し、同じ名前のプロパティの値をコピーして返してくれる。
AutoMapperは、コレクションのコピーにも対応している。CreateMapメソッドの呼び出し方は前と同じだが、Mapメソッドを呼び出すときにコレクションを渡せばよい(次のコード)。
// AutoMapperを使う(コレクション丸ごと)
AutoMapper.Mapper.CreateMap<Person, PersonForDisplay>();
var items = AutoMapper.Mapper.Map<IList<PersonForDisplay>>(data);
foreach (PersonForDisplay item in items)
personsList.Persons.Add(item);
' AutoMapperを使う(コレクション丸ごと)
AutoMapper.Mapper.CreateMap(Of Person, PersonForDisplay)()
Dim items = AutoMapper.Mapper.Map(Of IList(Of PersonForDisplay))(data)
For Each item As PersonForDisplay In items
personsList.Persons.Add(item)
Next
AutoMapperを使ってコレクション丸ごとのデータをコピーする例(上:C#、下:VB)
Mapメソッドの型引数にコピー先のコレクションの型を指定し、引数にコピー元のコレクションオブジェクトを渡す。CreateMapメソッドの呼び出しは前と同じで、コピー元とコピー先のクラスを登録する(コレクションのクラスではない)。
この例では、Mapメソッドから返されたコレクションをforeachループで再び詰め替えており、効率的ではない。Mapメソッドから返されたコレクションをそのままデータコンテキストに設定するような場合には、効率がよい。どちらの方法を使うべきかは、ケースバイケースである。
カテゴリ:オープンソース・ライブラリ 処理対象:データ型
カテゴリ:C# 処理対象:データ型
カテゴリ:Visual Basic 処理対象:データ型
Copyright© 1999-2018 Digital Advantage Corp. All Rights Reserved.