2012-08-17 200 views
6

我正在解析CSV文件並將數據放入結構中。我使用中的TextFieldParser,它的工作方式類似於魅力,但它返回String[]。目前,我有醜陋的過程:用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作爲一個intString S的。它讓我很惱火,他們不是很強類型的,但現在讓我們來看看。假設parser.ReadFields()返回一個String[]是否有一種更有效的方法來填充一個結構(可能將一些值,如row[0]需要成爲int)與數組中的值?

**編輯:**一個限制我忘了提,可能影響什麼樣的解決方案,將工作是,這個結構是[Serializable],將被髮送的TCP別處。

+1

使用反射。 – Grozz 2012-08-17 15:57:38

+0

反射肯定會效率較低,我只是住在它的 – RobJohnson 2012-08-17 16:01:22

+0

CsvHelper可能會對你很有幫助https://github.com/JoshClose/CsvHelper/wiki/Basics – KeesDijk 2012-08-17 16:01:38

回答

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; 
     } 
    } 
} 

這意味着該屬性可以簡單地用於與屬性命名Indexint值相關聯。

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

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

值然後可以與Type對象的結構類型設置(你可以得到它:

然後,您可以用該屬性(只是一些示範線),標誌着結構你的屬性與typeof運營商):

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); 
      } 
     } 
    } 
} 

此代碼迭代您的結構類型的所有屬性。對於每個屬性,它會檢查我們上面定義的自定義屬性類型。如果存在此類屬性,並且屬性類型爲stringint,則將從相應的數組索引中複製該值。

我檢查stringint屬性,這就是你在你的問題中提到的兩個數據類型。即使您現在只有一個包含int值的特定索引,但如果此代碼已準備好將任何索引作爲字符串或int屬性進行處理,那麼這對於可維護性來說也很有用。

注意,對於類型來處理數量更多,我建議不使用的ifelse if鏈,而是一個Dictionary<Type, Func<string, object>>該屬性類型映射到轉換功能。

0

使用反射作爲@Grozz建議在評論中。使用屬性標記結構類的每個屬性(即[ColumnOrdinal]),然後使用它來將信息與適當的列進行映射。如果您將double,decimal等作爲目標,則還應該考慮使用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模板。你可以在格式生成一個映射文件代碼

0,WorkOrder 
1,Description 
... 

載荷,併產生類似如下的方法:

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

這種方法在一些使用微型ORMs漂浮在其中並似乎工作得很好。

理想情況下,您的CSV將包含一行字段名稱,這將使這更容易。

或者,另一種方法,使用StructLayout以及動態方法來避免必須保留一個字段:column_index映射除了結構本身。

,或者,如果你想創造一些非常靈活,你可以標記使用自定義屬性上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; } 

} 

這允許您將每個屬性與列關聯。對於每一行,您可以遍歷所有屬性,並通過使用該屬性可以將正確的值賦給正確的屬性。你將不得不做一些從字符串到數字,日期和可能枚舉的類型轉換。您可以向屬性添加額外的屬性以幫助您完成該過程。在這個例子中我發明FormatDateTime被解析應該使用:

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不允許你一次讀一個字段,那麼你可以避開建築物和索引列陣列。