2012-08-17 3 views
6

私はCSVファイルを解析し、そのデータを構造体に配置しています。私はthis questionからTextFieldParserを使用しています。それはString[]を返す以外は魅力的です。現在、私は醜いの過程いますstructをString []で埋めますか?

String[] row = parser.ReadFields(); 
DispatchCall call = new DispatchCall(); 
if (!int.TryParse(row[0], out call.AccountID)) { 
    Console.WriteLine("Invalid Row: " + parser.LineNumber); 
    continue; 
} 
call.WorkOrder = row[1]; 
call.Description = row[2]; 
call.Date = row[3]; 
call.RequestedDate = row[4]; 
call.EstStartDate = row[5]; 
call.CustomerID = row[6]; 
call.CustomerName = row[7]; 
call.Caller = row[8]; 
call.EquipmentID = row[9]; 
call.Item = row[10]; 
call.TerritoryDesc = row[11]; 
call.Technician = row[12]; 
call.BillCode = row[13]; 
call.CallType = row[14]; 
call.Priority = row[15]; 
call.Status = row[16]; 
call.Comment = row[17]; 
call.Street = row[18]; 
call.City = row[19]; 
call.State = row[20]; 
call.Zip = row[21]; 
call.EquipRemarks = row[22]; 
call.Contact = row[23]; 
call.ContactPhone = row[24]; 
call.Lat = row[25]; 
call.Lon = row[26]; 
call.FlagColor = row[27]; 
call.TextColor = row[28]; 
call.MarkerName = row[29]; 

構造体は、アカウントIDがintことを除き、すべてのそれらのフィールドがStringのもので構成されています。それは強く型付けされていないと私を悩ませるものですが、今のところそれを見てみましょう。 parser.ReadFields()String[]を返すことを指定された配列内の値と(おそらくintなっする必要ようrow[0]などのいくつかの値を変換)構造体を充填するためのより効率的な方法がありますか?

**編集:** 1つの制限は、この構造体が[Serializable]であり、どこか別の場所でTcpに送信されるというソリューションの種類に影響を与えることを忘れていました。

+1

反射を使用します。 – Grozz

+0

リフレクションは間違いなく効率が悪く、ちょうどそれと一緒に暮らしています – RobJohnson

+0

CsvHelperはあなたに非常に役立つかもしれません。https://github.com/JoshClose/CsvHelper/wiki/Basics – KeesDijk

答えて

7

あなたの走行距離は、それがより良い解決策であるかどうかにより異なる可能性がありますが、あなたは、リフレクションを使用し、あなたとあなたの構造体のメンバをマークするために使用Attributeクラスを定義することができます。属性は配列のインデックスを引数として取ります。リフレクションを使用すると、正しい配列要素の値を割り当てることができます。

あなたはこのようなあなたの属性を定義することができます。

[AttributeUsage(AttributeTargets.Property)] 
public sealed class ArrayStructFieldAttribute : Attribute 
{ 
    public ArrayStructFieldAttribute(int index) 
    { 
     this.index = index; 
    } 

    private readonly int index; 

    public int Index { 
     get { 
      return index; 
     } 
    } 
} 

これは、属性は、単にプロパティでIndexという名前int値を関連付けるために使用できることを意味します。

その後、あなたはその属性(ちょうどいくつかの典型的なライン)と構造体にあなたの特性をマークすることができます:値は、あなたの構造体のタイプにTypeオブジェクトを使用して設定することができ

[ArrayStructField(1)] 
public string WorkOrder { // ... 

[ArrayStructField(19)] 
public string City { // ... 

を(あなたはそれを得ることができますあなたの構造体のタイプのすべてのプロパティを超える

foreach (PropertyInfo prop in structType.GetProperties()) { 
    ArrayStructFieldAttribute attr = prop.GetCustomAttributes(typeof(ArrayStructFieldAttribute), false).Cast<ArrayStructFieldAttribute>().FirstOrDefault(); 
    if (attr != null) { 
     // we have found a property that you want to load from an array element! 
     if (prop.PropertyType == typeof(string)) { 
      // the property is a string property, no conversion required 
      prop.SetValue(boxedStruct, row[attr.Index]); 
     } else if (prop.PropertyType == typeof(int)) { 
      // the property is an int property, conversion required 
      int value; 
      if (!int.TryParse(row[attr.Index], out value)) { 
       Console.WriteLine("Invalid Row: " + parser.LineNumber); 
      } else { 
       prop.SetValue(boxedStruct, value); 
      } 
     } 
    } 
} 

このコードを反復:typeofオペレーター)と。各プロパティについて、上で定義したカスタム属性タイプをチェックします。そのような属性が存在し、プロパティタイプがstringまたはintの場合、値はそれぞれの配列インデックスからコピーされます。

質問に記載されている2つのデータ型のとおり、stringintのプロパティを確認しています。あなたは今int値が含まれている唯一の特定のインデックスを持っているにもかかわらず、このコードは、文字列またはint型のプロパティとして任意のインデックスを処理するために準備されている場合、それが保守のために良いことです。

多くの種類の処理を行うには、ifelse ifのチェーンを使用せず、プロパティタイプを変換関数にマップするDictionary<Type, Func<string, object>>を使用することをお勧めします。 @Grozzコメントで示唆されているように

0

使用反射。属性(つまり、[ColumnOrdinal])で構造体クラスの各プロパティをマークし、これを使用して情報を適切な列にマップします。ターゲットとして2桁、10進数などを使用している場合は、Convert.ChangeTypeをターゲットタイプの適切な変換に使用することも検討する必要があります。パフォーマンスに満足できない場合は、DynamicMethodをその場で作成して楽しむことができます。さらに挑戦的ですが、実際には演奏と美があります。課題は、あなたが手作業で行った「配管」を行うためにIL命令をメモリに書き込むことです(私は通常、いくつかのサンプルコードを作成し、ILスパイを開始点として内部を調べます)。もちろん、そのような動的メソッドのどこかにキャッシュするので、それらの作成は一度だけ要求されます。

0

まず、リフレクションを使用してプロパティを反復し、属性値に基づいてstring[]の要素に一致させることです。

public struct DispatchCall 
{ 
    [MyAttribute(CsvIndex = 1)] 
    public string WorkOrder { get; set; } 
} 

MyAttributeはちょうどCSVのフィールド位置まで一致したインデックスを持つカスタム属性になります。

var row = parser.ReadFields(); 

    for each property that has MyAttribute... 
     var indexAttrib = MyAttribute attached to property 
     property.Value = row[indexAttrib.Index] 
    next 

(明らかに擬似コード、)あなたがそれらを反復処理することができるように

または

[StructLayout(LayoutKind.Sequential)] // keep fields in order 
public strict DispatchCall 
{ 
    public string WorkOrder; 
    public string Description; 
} 

StructLayout

が明示的に各フィールドの列番号を指定しなくても、順番に構造体のフィールドを維持します。これは、多くのフィールドがある場合、メンテナンスを節約できます。

それとも、あなたは完全にプロセスをスキップして、辞書にフィールド名を格納することができ:

ロードする方が簡単ですが、実際よりも、このあまりになり、強い型付け、を利用しない
var index = new Dictionary<int, string>(); 

/// populate index with row index : field name values, preferable from some sort of config file or database 
index[0] = "WorkOrder"; 
index[1] = "Description"; 
... 

var values = new Dictionary<string,object>(); 

for(var i=0;i<row.Length;i++) 
{ 
    values.Add(index[i],row[i]); 
} 

理想的。

ダイナミックメソッドまたはT4テンプレートを生成することもできます。このアプローチは、いくつかの中で使用されている

/// emit this 
    call.WorkOrder = row[0]; 
    call.Description = row[1]; 

など

:あなたはその

0,WorkOrder 
1,Description 
... 

負荷形式のマッピングファイルからコードを生成し、このようになります方法を生成することがありましたマイクロORMが浮遊していて、うまく機能しているようです。

理想的には、CSVにはフィールド名を含む行が含まれていることが理想的です。

また、StructLayoutを動的メソッドと一緒に使用すると、field:column_indexを構造体自体とは別にマッピングする必要がなくなります。

OR、あなたはカスタム属性を使用してDispatchCall上の各プロパティをマークすることができ、非常に柔軟なものを作成したい場合は列挙

public enum FieldIndex 
{ 
WorkOrder=0 
, 
Description // only have to specify explicit value for the first item in the enum 
, /// .... 
, 
MAX /// useful for getting the maximum enum integer value 
} 

for(var i=0;i<FieldIndex.MAX;i++) 
{ 
    var fieldName = ((FieldIndex)i).ToString(); /// get string enum name 
    var value = row[i]; 

    // use reflection to find the property/field FIELDNAME, and set it's value to VALUE. 
} 
1

を作成します。このようなもの:

class DispatchCall { 

    [CsvColumn(0)] 
    public Int32 AccountId { get; set; } 

    [CsvColumn(1)] 
    public String WorkOrder { get; set; } 

    [CsvColumn(3, Format = "yyyy-MM-dd")] 
    public DateTime Date { get; set; } 

} 

これにより、各プロパティを列に関連付けることができます。各行に対して、すべてのプロパティを反復処理し、属性を使用して適切な値を右のプロパティに割り当てることができます。文字列から数値、日付、おそらく列挙型への型変換を行う必要があります。そのプロセスに役立つように、属性に追加のプロパティを追加できます。例IにおいてDateTimeが解析されるときに使用されるべきであるFormatを考案:

Object ParseValue(String value, TargetType targetType, String format) { 
    if (targetType == typeof(String)) 
    return value; 
    if (targetType == typeof(Int32)) 
    return Int32.Parse(value); 
    if (targetType == typeof(DateTime)) 
    DateTime.ParseExact(value, format, CultureInfo.InvariantCulture); 
    ... 
} 

上記のコードでTryParse方法を使用してあなたが解析できない値が検出された複数のコンテキストを提供できるようにすることで、エラーハンドリングを向上させることができます。

このアプローチは、入力ファイルの各行に対してリフレクションコードが実行されるため、あまり効率的ではありません。これをより効率的にするには、DispatchCallを一度反映してコンパイルされたメソッドを動的に作成し、各行に適用できるようにする必要があります。可能ですが、特に簡単ではありません。

1

使用しているライブラリにどのくらい依存していますか?私はFile Helpersがこの種のもののために非常に有用であることを発見しました。あなたのコードは次のようになります:

using FileHelpers; 

// ... 

[DelimitedRecord(",")] 
class DispatchCall { 
    // Just make sure these are in order 
    public int AccountID { get; set; } 
    public string WorkOrder { get; set; } 
    public string Description { get; set; } 
    // ... 
} 

// And then to call the code 
var engine = new FileHelperEngine(typeof(DispatchCall)); 
engine.Options.IgnoreFirstLines = 1; // If you have a header row 
DispatchCall[] data = engine.ReadFile(FileName) as DispatchCall[]; 

これで、DispatchCallアレイが完成しました。エンジンはすべてあなたのために大変でした。

0

速度を上げようとしている場合は、脆弱なスイッチのステートメントになります。

var columns = parser.ReadFields(); 

for (var i = 0; i < columns.Length; i++) 
{ 
    SetValue(call, i, columns[i]); 
} 

private static void SetValue(DispatchCall call, int column, string value) 
{ 
    switch column 
    { 
     case 0: 
      SetValue(ref call.AccountId, (value) => int.Parse, value); 
      return; 

     case 1: 
      SetValue(ref call.WorkOrder, (value) => value, value); 
      return; 

     ... 

     default: 
      throw new UnexpectedColumnException(); 
    }  
} 

private static void SetValue<T>(
    ref T property, 
    Func<string, T> setter 
    value string) 
{ 
    property = setter(value); 
} 

そのTextFieldParserは、一度に一つのフィールドを読み取ることはできません、あなたが建物を避け、列配列のインデックスを作成することができることを恥。

関連する問題