C# + AutoMapperで、ArrayListをオブジェクトへマッピングする

AutoMapperを使うことで、オブジェクト同士を簡単にマッピングできます。

そんな中、ArrayListをオブジェクトへマッピングすることがあったため、対応したときのメモを残します。

 
目次

 

環境

 
今回は

new ArrayList {"1", "すいか", "夏", 1000};

のようなArrayList

public class Fruit
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Season { get; set; }
    public decimal UnitPrice { get; set; }
}

へとマッピングすることを考えます。

 

ArrayListをオブジェクトへマッピング

以下のstackoverflowを参考にしました。
c# - How to map an array to multiple properties using AutoMapper? - Stack Overflow

 
ただ、AutoMapperからstatic APIが取り除かれたため、それに対応して実装します。
Removing the static API from AutoMapper · Los Techies

 
まずはマッピングです。

// ArrayListの要素をプロパティにマッピング
var config = new MapperConfiguration(c => 
    c.CreateMap<ArrayList, Fruit>()
        .ForMember(d => d.Id, o => o.MapFrom(s => s[0]))
        .ForMember(d => d.Name, o => o.MapFrom(s => s[1]))
        .ForMember(d => d.Season, o => o.MapFrom(s => s[2]))
        .ForMember(d => d.UnitPrice, o => o.MapFrom(s => s[3]))
    );

// インスタンス化
var mapper = config.CreateMapper();

 
続いて変換します。Mapメソッドに、変換先のクラスを指定します。

// 元データ
var source = new ArrayList {"1", "すいか", "夏", 1000};

// 変換
var dest = mapper.Map<Fruit>(source);

 
結果を確認します。マッピングできています。

Console.WriteLine("ArrayList to Fruit");
Console.WriteLine(
    $"ID: {dest.Id}, Name: {dest.Name}, Season: {dest.Season}, UnitPrice: {dest.UnitPrice}");

// =>
// ArrayList to Fruit
// ID: 1, Name: すいか, Season: 夏, UnitPrice: 1000

 

ListをListへマッピング

AutoMapperではListなどもマッピングできますので、試してみます。
Lists and Arrays — AutoMapper documentation

マッピング方法は先ほどと変わりません。

var config = new MapperConfiguration(c => 
    c.CreateMap<ArrayList, Fruit>()
        .ForMember(d => d.Id, o => o.MapFrom(s => s[0]))
        .ForMember(d => d.Name, o => o.MapFrom(s => s[1]))
        .ForMember(d => d.Season, o => o.MapFrom(s => s[2]))
        .ForMember(d => d.UnitPrice, o => o.MapFrom(s => s[3]))
);

var mapper = config.CreateMapper();

 
Map()の使い方が変わります。

Map()の第一引数に変換元のクラスを、第二引数に変換先のクラスをそれぞれ指定します。

// データ
var source = new List<ArrayList>
{
    new ArrayList {"1", "すいか", "夏", 1000},
    new ArrayList {"2", "りんご", "秋", 100},
    new ArrayList {"3", "みかん", "冬", 150}
};

// 変換
var dest = mapper.Map<List<ArrayList>, List<Fruit>>(source);

 
結果です。List同士のマッピングもできました。

Console.WriteLine("List<ArrayList> to List<Fruit>");
foreach (var fruit in dest)
{
    Console.WriteLine(
        $"ID: {fruit.Id}, Name: {fruit.Name}, Season: {fruit.Season}, UnitPrice: {fruit.UnitPrice}");
}

// =>
// List<ArrayList> to List<Fruit>
// ID: 1, Name: すいか, Season: 夏, UnitPrice: 1000
// ID: 2, Name: りんご, Season: 秋, UnitPrice: 100
// ID: 3, Name: みかん, Season: 冬, UnitPrice: 150

 

双方向マッピング

AutoMapperでは、

を同時に設定できます。

逆方向をマッピングするには ReverseMap() を使います。
Reverse Mapping and Unflattening — AutoMapper documentation

通常同じプロパティへ自動的にマッピングしますが、今回はArrayListへのマッピングとなるため、手動でマッピングする必要があります。

そのため、 ConstructUsing() を使って、マッピングを定義しています。
https://docs.automapper.org/en/stable/Queryable-Extensions.html#custom-destination-type-constructors

var config = new MapperConfiguration(c => 
    c.CreateMap<ArrayList, Fruit>()
        .ForMember(d => d.Id, o => o.MapFrom(s => s[0]))
        .ForMember(d => d.Name, o => o.MapFrom(s => s[1]))
        .ForMember(d => d.Season, o => o.MapFrom(s => s[2]))
        .ForMember(d => d.UnitPrice, o => o.MapFrom(s => s[3]))
        
        // 逆マップ
        .ReverseMap()
        .ConstructUsing(x => new ArrayList
        {
            x.Id, x.Name, x.Season, x.UnitPrice
        })
);
var mapper = config.CreateMapper();

 

使い方です。

// 結果
var reverseSource = new Fruit {Id = 1, Name = "すいか", Season = "夏", UnitPrice = 1000};

// 変換
var reverseDst = mapper.Map<ArrayList>(reverseSource);

 
結果確認です。

Console.WriteLine(
    $"ID: {reverseDst[0]}, Name: {reverseDst[1]}, Season: {reverseDst[2]}, UnitPrice: {reverseDst[3]}");

// =>
// 逆方向
// ID: 1, Name: すいか, Season: 夏, UnitPrice: 1000

 

双方向マッピング (List)

Listも可能です。

定義は変わりません。

var config = new MapperConfiguration(c => 
    c.CreateMap<ArrayList, Fruit>()
        .ForMember(d => d.Id, o => o.MapFrom(s => s[0]))
        .ForMember(d => d.Name, o => o.MapFrom(s => s[1]))
        .ForMember(d => d.Season, o => o.MapFrom(s => s[2]))
        .ForMember(d => d.UnitPrice, o => o.MapFrom(s => s[3]))
        
        // 逆マップ
        .ReverseMap()
        .ConstructUsing(x => new ArrayList
        {
            x.Id, x.Name, x.Season, x.UnitPrice
        })
);
var mapper = config.CreateMapper();

 
使い方が変わります。

Map()の第一引数が変換元のList、第二引数が変換先のListを指定します。

var reverseSourceList = new List<Fruit>
{
    new Fruit {Id = 1, Name = "すいか", Season = "夏", UnitPrice = 1000},
    new Fruit {Id = 2, Name = "りんご", Season = "秋", UnitPrice = 100},
    new Fruit {Id = 3, Name = "みかん", Season = "冬", UnitPrice = 150}

};

// 変換
var reverseDestList = mapper.Map<List<Fruit>, List<ArrayList>>(reverseSourceList);

// 結果
foreach (var dst in reverseDestList)
{
    Console.WriteLine(
        $"ID: {dst[0]}, Name: {dst[1]}, Season: {dst[2]}, UnitPrice: {dst[3]}");
}

// =>
// 逆方向List
// ID: 1, Name: すいか, Season: 夏, UnitPrice: 1000
// ID: 2, Name: りんご, Season: 秋, UnitPrice: 100
// ID: 3, Name: みかん, Season: 冬, UnitPrice: 150

 

マッピング設定をクラス化 (Profile)

Profileを使うことで、マッピング設定をクラス化できます。 https://docs.automapper.org/en/stable/Configuration.html#profile-instances

先ほどの双方向マッピングをクラスにします。  

public class AutoMapperProfile : Profile
{
    public AutoMapperProfile()
    {
        CreateMap<ArrayList, Fruit>()
            .ForMember(d => d.Id, o => o.MapFrom(s => s[0]))
            .ForMember(d => d.Name, o => o.MapFrom(s => s[1]))
            .ForMember(d => d.Season, o => o.MapFrom(s => s[2]))
            .ForMember(d => d.UnitPrice, o => o.MapFrom(s => s[3]))
            
            // 逆マップ
            .ReverseMap()
            .ConstructUsing(x => new ArrayList
            {
                x.Id, x.Name, x.Season, x.UnitPrice
            });
    }
}

 
設定クラスを読み込みます。

AutoMapperでは明示的に指定する他、アセンブリから自動で検索することもできます。
http://docs.automapper.org/en/stable/Configuration.html#assembly-scanning-for-auto-configuration

今回はアセンブリを指定してみます。

// Profileを検索
var config = new MapperConfiguration(c => 
    c.AddMaps(new []
    {
        "MyApp",
    })
);

// インスタンス化
var mapper = config.CreateMapper();

 
あとは今までと同じです。

ここでは正方向だけ示しますが、逆方向も動作します。

Console.WriteLine("正方向 (Profile使用)");
var source = new ArrayList {"1", "すいか", "夏", 1000};
var dest = mapper.Map<Fruit>(source);

Console.WriteLine(
    $"ID: {dest.Id}, Name: {dest.Name}, Season: {dest.Season}, UnitPrice: {dest.UnitPrice}");

// =>
// 正方向 (Profile使用)
// ID: 1, Name: すいか, Season: 夏, UnitPrice: 1000

 

ソースコード

GitHubに上げました。
thinkAmi-sandbox/AutoMapper-sample