2018/08/01(水)【Unity】CSVファイルからListを生成する

マスターデータなんかのリスト作成に。

参考

CSVファイルをコード1行でListに変換する 【Unity】【C#】 | Unity開発Tips

ほとんどコピペ。引数周りだけ変更。

nsCSVReader.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.IO;
using UnityEngine;

/// <summary>
/// CSVの列とのマッピングのための属性クラス
/// </summary>
public class CsvColumnAttribute : Attribute
{
	public CsvColumnAttribute(int columnIndex)
		: this(columnIndex, null)
	{
	}
	public CsvColumnAttribute(int columnIndex, object defaultValue)
	{
		this.ColumnIndex = columnIndex;
		this.DefaultValue = defaultValue;
	}
	public int ColumnIndex { get; set; }
	public object DefaultValue { get; set; }
}

public class nsCSVReader<T> : IEnumerable<T>, IDisposable
	where T : class, new()
{
	/// <summary>
	/// Type毎のデータコンバーター
	/// </summary>
	private Dictionary<Type, TypeConverter> converters = new Dictionary<Type, TypeConverter>();

	/// <summary>
	/// 列番号をキーとしてフィールド or プロパティへのsetメソッドが格納されます。
	/// </summary>
	private Dictionary<int, Action<object, string>> setters = new Dictionary<int, Action<object, string>>();

	/// <summary>
	/// Tの情報をロードします。
	/// setterには列番号をキーとしたsetメソッドが格納されます。
	/// </summary>
	private void LoadType()
	{
		Type type = typeof(T);

		// Field, Property のみを対象とする
		var memberTypes = new MemberTypes[] { MemberTypes.Field, MemberTypes.Property };

		// インスタンスメンバーを対象とする
		BindingFlags flag = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		foreach (MemberInfo member in type.GetMembers(flag).Where((member) => memberTypes.Contains(member.MemberType)))
		{
			CsvColumnAttribute csvColumn = GetCsvColumnAttribute(member);

			if (csvColumn == null) continue;

			int columnIndex = csvColumn.ColumnIndex;
			object defaultValue = csvColumn.DefaultValue;



			if (member.MemberType == MemberTypes.Field)
			{
				// field
				FieldInfo fieldInfo = type.GetField(member.Name, flag);
				setters[columnIndex] = (target, value) =>
					fieldInfo.SetValue(target, GetConvertedValue(fieldInfo, value, defaultValue));
			}
			else
			{
				// property
				PropertyInfo propertyInfo = type.GetProperty(member.Name, flag);
				setters[columnIndex] = (target, value) =>
					propertyInfo.SetValue(target, GetConvertedValue(propertyInfo, value, defaultValue), null);
			}
		}
	}

	/// <summary>
	/// 対象のMemberInfoからCsvColumnAttributeを取得する
	/// </summary>
	/// <param name="member">確認対象のMemberInfo</param>
	/// <returns>CsvColumnAttributeのインスタンス、設定されていなければnull</returns>
	private CsvColumnAttribute GetCsvColumnAttribute(MemberInfo member)
	{
		return (CsvColumnAttribute)member.GetCustomAttributes(typeof(CsvColumnAttribute), true).FirstOrDefault();
		//return member.GetCustomAttributes<CsvColumnAttribute>().FirstOrDefault();
	}

	/// <summary>
	/// valueを対象のTypeへ変換する。できない場合はdefaultを返す
	/// </summary>
	/// <param name="type">変換後の型</param>
	/// <param name="value">変換元の値</param>
	/// <param name="default">規定値</param>
	/// <returns></returns>
	private object GetConvertedValue(MemberInfo info, object value, object @default)
	{
		Type type = null;
		if (info is FieldInfo)
		{
			type = (info as FieldInfo).FieldType;
		}
		else if (info is PropertyInfo)
		{
			type = (info as PropertyInfo).PropertyType;
		}

		// コンバーターは同じTypeを使用することがあるため、キャッシュしておく
		if (!converters.ContainsKey(type))
		{
			converters[type] = TypeDescriptor.GetConverter(type);
		}

		TypeConverter converter = converters[type];

		////変換できない場合に例外を受け取りたい場合
		//return converter.ConvertFrom(value);

		//失敗した場合に CsvColumnAttribute の規定値プロパティを返す場合
		try
		{
			// 変換した値を返す。
			return converter.ConvertFrom(value);
		}
		catch (Exception)
		{
			// 変換できなかった場合は規定値を返す
			return @default;
		}
	}


	private StringReader reader;
	private bool skipFirstLine;
	private Encoding encoding;

	public nsCSVReader(TextAsset textAsset)
		: this(textAsset, true)
	{
	}

	public nsCSVReader(TextAsset textAsset, bool skipFirstLine)
		: this(textAsset, skipFirstLine, null)
	{
	}

	public nsCSVReader(TextAsset textAsset, bool skipFirstLine, Encoding encoding)
	{
		this.skipFirstLine = skipFirstLine;
		this.encoding = encoding;

		// 既定のエンコードの設定
		if (this.encoding == null)
		{
			this.encoding = System.Text.Encoding.GetEncoding("utf-8");
		}

		// Tを解析する
		LoadType();
		TextAsset csv = textAsset;

		this.reader = new StringReader(csv.text);
		// ヘッダーを飛ばす場合は1行読む
		if (skipFirstLine)
		{
			this.reader.ReadLine();
		}
	}

	public void Dispose()
	{
		using (reader)
		{
		}
		reader = null;
	}

	public IEnumerator<T> GetEnumerator()
	{
		string line;
		while ((line = reader.ReadLine()) != null)
		{
			// T のインスタンスを作成
			var data = new T();

			// 行をセパレータで分解
			string[] fields = line.Split(',');

			// セル数分だけループを回す
			foreach (int columnIndex in Enumerable.Range(0, fields.Length))
			{
				// 列番号に対応するsetメソッドがない場合は処理しない
				if (!setters.ContainsKey(columnIndex)) continue;

				// setメソッドでdataに値を入れる
				setters[columnIndex](data, fields[columnIndex]);
			}

			yield return data;
		}
	}

	System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
	{
		return this.GetEnumerator();
	}
}

/// <summary>
/// 変換失敗時のイベント引数クラス
/// </summary>
public class ConvertFailedEventArgs : EventArgs
{
	public ConvertFailedEventArgs(MemberInfo info, object value, object defaultValue, Exception ex)
	{
		this.MemberInfo = info;
		this.FailedValue = value;
		this.CorrectValue = defaultValue;
		this.Exception = ex;
	}

	/// <summary>
	/// 変換に失敗したメンバーの情報
	/// </summary>
	public MemberInfo MemberInfo { get; private set; }

	/// <summary>
	/// 失敗時の値
	/// </summary>
	public object FailedValue { get; private set; }

	/// <summary>
	/// 正しい値をイベントで受け取る側が設定してください。規定値はCsvColumnAttribute.DefaultValueです。
	/// </summary>
	public object CorrectValue { get; set; }

	/// <summary>
	/// 発生した例外
	/// </summary>
	public Exception Exception { get; private set; }
}

nsClassList.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

/// <summary>
/// Ns class list.
/// </summary>
public abstract class nsClassList<T> : IEnumerable<T> where T : class, new()
{
	private List<T> list = new List<T>();

	protected nsClassList()
	{
	}

	protected nsClassList(TextAsset textAsset, bool _skipFirstLine)
	{
		using (var reader = new nsCSVReader<T>(textAsset, _skipFirstLine))
		{
			list = reader.ToList();
		}
	}

	public void Add(T item)
	{
		list.Add(item);
	}

	public void RemoveAt(int index)
	{
		list.RemoveAt(index);
	}

	public void Remove(T item)
	{
		list.Remove(item);
	}

	public T this[int index]
	{
		get { return this.list[index]; }
		set { this.list[index] = value; }
	}

	public int Count { get { return list.Count; } }

	public IEnumerator<T> GetEnumerator()
	{
		for (int i = 0; i < list.Count; i++)
		{
			yield return list[i];
		}
	}

	System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
	{
		return this.GetEnumerator();
	}
}

PlayerMaster.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 自機のマスターデータ。
/// </summary>
public class PlayerMaster
{
	[CsvColumnAttribute(0, 0)]
	public int id { get; set; }

	[CsvColumnAttribute(1, 0)]
	public int body_id { get; set; }

	[CsvColumnAttribute(2, 0)]
	public int bullet_id { get; set; }

	[CsvColumnAttribute(3, 0)]
	public int hp { get; set; }

	[CsvColumnAttribute(4, 0)]
	public int spd { get; set; }

	public override string ToString()
	{
		return string.Format("[PlayerMaster: id={0}, body_id={1}, bullet_id={2}, hp={3}, spd={4}]", id, body_id, bullet_id, hp, spd);
	}	
}

/// <summary>
/// ClassListを継承したPlayerModelListを定義
/// </summary>
public class PlayerMasterList : nsClassList<PlayerMaster>
{
	public PlayerMasterList(TextAsset textAsset, bool _skipFirstLine) : base(textAsset, _skipFirstLine) { }
}

使い方

PlayerMasterList PlayerMasterList	= new PlayerMasterList(TextAsset, true);
OK キャンセル 確認 その他