LoginSignup
5
5

More than 1 year has passed since last update.

CSV をクラスにバインドする (C#)

Last updated at Posted at 2020-04-29

CSV をクラスにバインドする (C#)

例えば

RowId,Id,StartDate,StopDate,CustomerId
0,C0001,2020-03-01T00:00:00.0000000,2020-04-01T00:00:00.0000000,0
1,C0002,2020-03-01T00:00:00.0000000,,1

みたいな CSV があって

public class Contract
{
    public int RowId { get; set; }
    public string Id { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime? StopDate { get; set; }
    public int CustomerId { get; set; }
}

にバインドしたいという話です. CSV パーサは今回は話題にしたくないので、string.Split(",") で片付くということにしましょう.

以下みたいなコードを書くと、var contracts = Load<Contract>(fstream).ToArray(); でバインドできます.

public static T Bind<T>(Dictionary<string, int> headerMap, string[] values)
    where T : class, new()
{
    var result = new T();
    foreach (var p in typeof(T).GetProperties())
    {
        if (!headerMap.ContainsKey(p.Name)) continue;
        var value = values[headerMap[p.Name]];

        var type = Nullable.GetUnderlyingType(p.PropertyType);
        if (type == null)
        {
            type = p.PropertyType;
        }
        else
        {
            if (value == "")
            {
                p.SetValue(result, null);
                continue;
            }
        }

        if (type == typeof(int))
        {
            p.SetValue(result, int.Parse(value));
        }
        else if (type == typeof(bool))
        {
            p.SetValue(result, bool.Parse(value));
        }
        else if (type == typeof(string))
        {
            p.SetValue(result, value);
        }
        else if (type == typeof(decimal))
        {
            p.SetValue(result, decimal.Parse(value));
        }
        else if (type == typeof(Guid))
        {
            p.SetValue(result, Guid.Parse(value));
        }
        else if (type == typeof(DateTime))
        {
            if (!DateTime.TryParseExact(value, "o", null, DateTimeStyles.RoundtripKind, out var _))
            {
                value = DateTime.Parse(value).ToString("o");
            }
            p.SetValue(result, DateTime.ParseExact(value, "o", null, DateTimeStyles.RoundtripKind));
        }
        else if (type.IsEnum)
        {
            p.SetValue(result, Enum.Parse(type, value));
        }
        else
        {
            throw new ApplicationException($"Unsupported type: {typeof(T).Name}.");
        }
    }
    return result;
}

public static IEnumerable<T> Load<T>(Stream stream, Encoding encoding = null)
    where T : class, new()
{
    if (encoding == null) encoding = Encoding.UTF8;
    using (var reader = new StreamReader(stream, encoding))
    {
        var header = reader.ReadLine().Split(",");
        var headerMap = new Dictionary<string, int>();
        for (var i = 0; i < header.Length; i++)
        {
            headerMap[header[i]] = i;
        }

        while (true)
        {
            var line = reader.ReadLine();
            if (line == null) break;
            yield return Bind<T>(headerMap, line.Split(","));
        }
    }
}

なお、プロパティではなくフィールドにしたい場合には .GetProperties().GetFields() になり、.PropertyType.FieldType になります.

また、class ではなく struct にしたい場合には、where T : class, new()where T : struct になり、var result = new T();var result = default(T); になり、p.SetValue(result, v);p.SetValueDirect(__makeref(result), v); になります.

関連記事: クラスを CSV にデバインドする (C#)

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5