オープンソースのライブラリ「AutoMapper」を使い、オブジェクト間でのデータのコピーという煩雑な処理を数行で実現する方法を解説する。
対象:.NET 4以降
あるオブジェクトのプロパティ値を別のオブジェクトのプロパティ値にコピーするコードは、よく書くものだ。特にMVVMなどのアーキテクチャでロジックと画面を分離するときには、頻繁に記述することになる。そのようなコードの記述は退屈だし、それ故、間違いも入り込みやすい。自動化できたらよいのにと思ったことはないだろうか? それを実現する「AutoMapper」というオープンソースのライブラリがある。本稿では、AutoMapperの基本的な使い方を紹介する。
なお、本稿執筆時点でAutoMapperがサポートしているのは.NET Framework 4以降であるが、NuGetから導入するため、本稿ではVisual Studio 2012を使って説明する*1。また、本稿のサンプルは「MSDN Code Recipe:.NET Tips #1102」からダウンロードできる。
*1 NuGetから導入する機能はVisual Studio 2012から標準搭載になった。Visual Studio 2010でNuGetを利用したい場合は、「特集:.NET開発の新標準「NuGet」入門(前編)〜.NETで開発モジュール導入が楽々に! NuGet入門」を参考にしてほしい。
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()
{
var list = new List<Person>();
// ダミーのデータ(実際にはデータベースなどから取得してくると思ってほしい)
list.Add(new Person() { LastName = "八洲", FirstName = "未來", });
list.Add(new Person() { LastName = "山本", FirstName = "康彦", });
list.Add(new Person() { LastName = "嶺", FirstName = "阿室", });
return list;
}
}
Public Class Person
Public Property FirstName As String
Public Property LastName As String
Public Shared Function GetData() As IList(Of Person)
Dim list = New List(Of Person)()
' ダミーのデータ(実際にはデータベースなどから取得してくると思ってほしい)
list.Add(New Person() With {.LastName = "八洲", .FirstName = "未來"})
list.Add(New Person() With {.LastName = "山本", .FirstName = "康彦"})
list.Add(New Person() With {.LastName = "嶺", .FirstName = "阿室"})
Return list
End Function
End Class
次に、「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プロパティ
// 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (string.Equals(_firstName, value))
return;
_firstName = value;
NotifyPropertyChanged("FirstName");
NotifyPropertyChanged("DisplayName");
}
}
// LastNameプロパティ
// 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
if (string.Equals(_lastName, value))
return;
_lastName = value;
NotifyPropertyChanged("LastName");
NotifyPropertyChanged("DisplayName");
}
}
// 画面に表示したいDisplayNameプロパティ
public string DisplayName
{
get { return string.Format("{0} {1}", LastName, FirstName); }
}
}
Public Class PersonForDisplay
Implements ComponentModel.INotifyPropertyChanged
' 公開するイベントハンドラー(INotifyPropertyChangedインターフェースの実装)
Public Event PropertyChanged(sender As Object, e As ComponentModel.PropertyChangedEventArgs) _
Implements ComponentModel.INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(info As String)
RaiseEvent PropertyChanged(Me, _
New System.ComponentModel.PropertyChangedEventArgs(info))
End Sub
' FirstNameプロパティ
' 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
Private _firstName As String
Public Property FirstName As String
Get
Return _firstName
End Get
Set(value As String)
If (String.Equals(_firstName, value)) Then
Return
End If
_firstName = value
NotifyPropertyChanged("FirstName")
NotifyPropertyChanged("DisplayName")
End Set
End Property
' LastNameプロパティ
' 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
Private _lastName As String
Public Property LastName As String
Get
Return _lastName
End Get
Set(value As String)
If (String.Equals(_lastName, value)) Then
Return
End If
_lastName = value
NotifyPropertyChanged("LastName")
NotifyPropertyChanged("DisplayName")
End Set
End Property
' 画面に表示したいDisplayNameプロパティ
Public ReadOnly Property DisplayName As String
Get
Return String.Format("{0} {1}", LastName, FirstName)
End Get
End Property
End Class
また、画面には「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()
' デザイン時に表示するダミーデータを設定する
' ……省略……
End Sub
End Class
この「PersonsList」クラスをWPFのListViewコントロール(System.Windows.Controls名前空間)にバインドして表示させる例を、次の画像に示す。
AutoMapperを使わない場合は、画面が表示されるときなどに、次のコードのようにしてプロパティ値を逐一コピーしなければならない。
// ロジックを呼び出してデータを取得する
IList<Person> data = Person.GetData();
// 表示用のコレクションを用意する
var personsList = new PersonsList();
// データを表示用のコレクションにコピーする(AutoMapper未使用)
foreach (Person p in data)
{
personsList.Persons.Add(
new PersonForDisplay() {
FirstName = p.FirstName,
LastName = p.LastName,
// ここでは二つだけだが、一般にはうんざりするほどの代入文を書くことになる
}
);
}
// 表示用のコレクションをデータコンテキストにセット
this.DataContext = personsList;
' ロジックを呼び出してデータを取得する
Dim data As IList(Of Person) = Person.GetData()
' 表示用のコレクションを用意する
Dim personsList = New PersonsList()
' データを表示用のコレクションにコピーする(AutoMapper未使用)
For Each p As Person In data
personsList.Persons.Add( _
New PersonForDisplay() _
With {
.FirstName = p.FirstName,
.LastName = p.LastName
}
)
' ここでは二つだけだが、一般にはうんざりするほどの代入文を書く
Next
' 表示用のコレクションをデータコンテキストにセット
Me.DataContext = personsList
上のコードでは、同じ名前のプロパティ値をコピーするコードが何行も続く(サンプルなので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を使うには、まず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
カテゴリ:オープンソース・ライブラリ 処理対象:データ型
カテゴリ:C# 処理対象:データ型
カテゴリ:Visual Basic 処理対象:データ型
Copyright© Digital Advantage Corp. All Rights Reserved.