項目名は、決まったものではなく、どんな項目が入ってくるかわかりません。
また項目数も、ファイルによってマチマチです。
項目名の順序もマチマチという場合があります。
普通csvって
csv
1 場所,取引先,商品
2 東京,株式会社A,食品
3 大阪,B株式会社,飲料
こうだと思うんですけど、扱うファイルはなぜか「2個ずつ組になった1行のファイル」ということでしょうか?
単純にやるならDictionary
かなんかでいいんじゃないでしょうか。
Dictionary<TKey,TValue> クラス (System.Collections.Generic) | Microsoft Docs
どうしてもプロパティっぽく書きたいのであればExpandoObject
が使えますが、便利かどうかはわかりません。
ExpandoObject クラス (System.Dynamic) | Microsoft Docs
cs
1 using System ;
2 using System . Collections . Generic ;
3 using System . Dynamic ;
4 using System . IO ;
5 using System . Linq ;
6
7 namespace Q8aid4d1edch6jx
8 {
9 class Program
10 {
11 static void Main ( )
12 {
13 File . WriteAllText ( "csv1.csv" , "場所,東京,取引先,株式会社A,商品,食品" ) ;
14 File . WriteAllText ( "csv2.csv" , "実績,あり,商品,食品,取引先,株式会社A,場所,東京,実店舗,あり" ) ;
15
16
17 var csv1 = new Dictionary < string , string > ( ) ; // 辞書(Dictionary)を作る
18 var line1 = File . ReadLines ( "csv1.csv" ) . Single ( ) ; // 1行読み込む(1行でなかったらエラーFirstのほうがいいかも?)
19 var split1 = line1 . Split ( new char [ ] { ',' } ) ; // カンマで区切る
20 for ( var i = 0 ; i < split1 . Length ; i += 2 ) // 2個セットで使うので+=2
21 {
22 csv1 . Add ( split1 [ i ] , split1 [ i + 1 ] ) ; // 2個セットの前をキー後ろを値に
23 }
24
25 Console . WriteLine ( csv1 [ "場所" ] ) ; // 東京 キーから値を取得(キーがないとKeyNotFoundException)
26 Console . WriteLine ( csv1 . TryGetValue ( "実績" , out var value ) ? value : "項目なし" ) ; // 項目なし キーがあるかどうかわからない場合はTryGetValue
27
28
29 dynamic csv2 = new ExpandoObject ( ) ;
30
31 var line2 = File . ReadLines ( "csv2.csv" ) . Single ( ) ;
32 var split2 = line2 . Split ( new char [ ] { ',' } ) ;
33 for ( var i = 0 ; i < split2 . Length ; i += 2 )
34 {
35 var dic = ( IDictionary < string , object > ) csv2 ; // ExpandoObjectはIDictionaryにキャストできる
36 dic . Add ( split2 [ i ] , split2 [ i + 1 ] ) ; // Dictionaryと同じようにAddするとプロパティが生えてくるように見える
37 }
38
39 Console . WriteLine ( csv2 . 場所 ) ; // 東京 プロパティのようにアクセスできるが、インテリセンスが効くわけでもないので便利かどうかはわからない
40 Console . WriteLine ( csv2 . 実績 ) ; // あり
41 //Console.WriteLine(csv2.ほげ); // RuntimeBinderException
42 }
43 }
44 }
DataGridView
がマストとなると、Datatable
を選択すべきでしょう。
当初の回答とは方向性が変わりましたが、やること自体は同じような感じです(Dictionary
の代わりにDatatable
になっただけ)
どうせなのでより実践的?(エンコードやまともなSplit
)にしました。
CsvHelperでもなんでもいいのですが、変則csvを扱いやすかったので↓を使用しました。
NuGet Gallery | Csv 2.0.84
出番がないのはかわいそうなので、PropertyGrid
もついでに付けましたw
DataGridView
で複数行選択すると、PropertyGrid
で値を一括変更できます(なぜか単一選択と複数選択で項目の並び順が変わる。なんで??)
cs
1 using System ;
2 using System . Collections . Generic ;
3 using System . ComponentModel ;
4 using System . Data ;
5 using System . Diagnostics ;
6 using System . IO ;
7 using System . Linq ;
8 using System . Text ;
9 using System . Windows . Forms ;
10 using Csv ;
11
12 namespace QQ8aid4d1edch6jx
13 {
14 public partial class Form1 : Form
15 {
16 private readonly DataTable dataTable = new DataTable ( ) ;
17
18 public Form1 ( )
19 {
20 InitializeComponent ( ) ;
21 openFileDialog1 . Filter = "CSVファイル|*.csv" ;
22 dataGridView1 . DataSource = dataTable ;
23 dataGridView1 . SelectionMode = DataGridViewSelectionMode . FullRowSelect ;
24 propertyGrid1 . ToolbarVisible = false ;
25
26 File . WriteAllText ( "csv1.csv" , "場所,東京,取引先,株式会社A,商品,食品" , Encoding . GetEncoding ( "shift_jis" ) ) ;
27 File . WriteAllText ( "csv2.csv" , "実績,あり,商品,文具,取引先,株式会社B,場所,大阪,実店舗,あり" , Encoding . GetEncoding ( "shift_jis" ) ) ;
28 }
29
30 // 変則csvの読み込み
31 private void LoadButton_Click ( object sender , EventArgs e )
32 {
33 if ( openFileDialog1 . ShowDialog ( ) == DialogResult . OK )
34 {
35 DataRow row = dataTable . NewRow ( ) ;
36
37 // 値にカンマや改行が含まれることも考慮しちゃんとパースする
38 string csv = File . ReadAllText ( openFileDialog1 . FileName , Encoding . GetEncoding ( "shift_jis" ) ) ;
39 var options = new CsvOptions { HeaderMode = HeaderMode . HeaderAbsent , AllowNewLineInEnclosedFieldValues = true , } ;
40 string [ ] split = CsvReader . ReadFromText ( csv , options ) . First ( ) . Values ;
41
42 // カラム数が奇数の場合はどうすべきか?(とりあえずハブったものは無視)
43 for ( var i = 0 ; i < split . Length - 1 ; i += 2 )
44 {
45 string key = split [ i ] ;
46 string value = split [ i + 1 ] ;
47
48 if ( ! dataTable . Columns . Contains ( key ) ) // まだ該当カラムがなければ...
49 {
50 dataTable . Columns . Add ( key ) ; // カラム追加
51 }
52 row [ key ] = value ;
53 }
54
55 dataTable . Rows . Add ( row ) ;
56 }
57 }
58
59 // DataTableをまともなcsvに出力
60 private void SaveButton_Click ( object sender , EventArgs e )
61 {
62 var header = new List < string > ( ) ; // csvヘッダー部分
63 foreach ( DataColumn column in dataTable . Columns )
64 {
65 header . Add ( column . Caption ) ; // カラムヘッダーを順番に追加
66 }
67
68 var data = new List < string [ ] > ( ) ; // csvデータ部分(2次元配列のような状態)
69 foreach ( DataRow row in dataTable . Rows )
70 {
71 var items = new List < string > ( ) ; // 1行分
72 foreach ( object item in row . ItemArray )
73 {
74 items . Add ( item . ToString ( ) ) ; // 1セル分
75 }
76 data . Add ( items . ToArray ( ) ) ;
77 }
78
79 // ダブルクォート処理等いい感じにやってくれる
80 var csv = CsvWriter . WriteToText ( header . ToArray ( ) , data , ',' ) ;
81
82 // 上記foreachをワンライナーで書くなら
83 //var header = dataTable.Columns.Cast<DataColumn>().Select(x => x.Caption).ToArray();
84 //var data = dataTable.Rows.Cast<DataRow>().Select(x => x.ItemArray.Select(y => y.ToString()).ToArray());
85 //var csv = CsvWriter.WriteToText(header, data, ',');
86
87 File . WriteAllText ( "out.csv" , csv , Encoding . GetEncoding ( "shift_jis" ) ) ;
88 Process . Start ( "out.csv" ) ;
89 }
90
91 private void DataGridView1_SelectionChanged ( object sender , EventArgs e )
92 {
93 propertyGrid1 . SelectedObjects = dataGridView1 . SelectedRows
94 . Cast < DataGridViewRow > ( )
95 . Select ( x => new RowWrapper ( ( DataRowView ) x . DataBoundItem ) )
96 . ToArray ( ) ;
97 }
98
99 private void PropertyGrid1_PropertyValueChanged ( object s , PropertyValueChangedEventArgs e )
100 {
101 dataGridView1 . Refresh ( ) ;
102 }
103 }
104
105
106 // DataRowをいい感じにPropertyGridに表示する
107 // [C#/winforms: how to best bind a propertygrid and a System.Data.DataRow - Stack Overflow](https://stackoverflow.com/questions/943621/c-winforms-how-to-best-bind-a-propertygrid-and-a-system-data-datarow)
108 [ TypeConverter ( typeof ( RowWrapperConverter ) ) ]
109 class RowWrapper
110 {
111 public List < string > Exclude { get ; } = new List < string > ( ) ;
112 private readonly DataRowView rowView ;
113 public RowWrapper ( DataRowView rowView ) => this . rowView = rowView ;
114 private static DataRowView GetRowView ( object component ) => ( ( RowWrapper ) component ) . rowView ;
115 private class RowWrapperConverter : TypeConverter
116 {
117 public override bool GetPropertiesSupported ( ITypeDescriptorContext context ) => true ;
118 public override PropertyDescriptorCollection GetProperties ( ITypeDescriptorContext context , object value , Attribute [ ] attributes )
119 {
120 var rw = ( RowWrapper ) value ;
121 var props = TypeDescriptor . GetProperties ( GetRowView ( value ) , attributes ) ;
122 var result = new List < PropertyDescriptor > ( props . Count ) ;
123 foreach ( PropertyDescriptor prop in props )
124 {
125 if ( rw . Exclude . Contains ( prop . Name ) ) continue ;
126 result . Add ( new RowWrapperDescriptor ( prop ) ) ;
127 }
128 return new PropertyDescriptorCollection ( result . ToArray ( ) ) ;
129 }
130 }
131 private class RowWrapperDescriptor : PropertyDescriptor
132 {
133 private static Attribute [ ] GetAttribs ( AttributeCollection value )
134 {
135 if ( value == null ) return null ;
136 var result = new Attribute [ value . Count ] ;
137 value . CopyTo ( result , 0 ) ;
138 return result ;
139 }
140 private readonly PropertyDescriptor innerProp ;
141 public RowWrapperDescriptor ( PropertyDescriptor innerProperty ) : base ( innerProperty . Name , GetAttribs ( innerProperty . Attributes ) ) => innerProp = innerProperty ;
142 public override bool ShouldSerializeValue ( object component ) => innerProp . ShouldSerializeValue ( GetRowView ( component ) ) ;
143 public override void ResetValue ( object component ) => innerProp . ResetValue ( GetRowView ( component ) ) ;
144 public override bool CanResetValue ( object component ) => innerProp . CanResetValue ( GetRowView ( component ) ) ;
145 public override void SetValue ( object component , object value ) => innerProp . SetValue ( GetRowView ( component ) , value ) ;
146 public override object GetValue ( object component ) => innerProp . GetValue ( GetRowView ( component ) ) ;
147 public override Type PropertyType => innerProp . PropertyType ;
148 public override Type ComponentType => typeof ( RowWrapper ) ;
149 public override bool IsReadOnly => innerProp . IsReadOnly ;
150 }
151 }
152 }