2012-08-17 15 views
6

मैं एक सीएसवी फ़ाइल पार्स कर रहा हूं और डेटा को एक स्ट्रक्चर में रख रहा हूं। मैं this question से उपयोग कर रहा हूं और यह एक आकर्षण की तरह काम कर रहा है सिवाय इसके कि यह String[] देता है। struct उन सभी क्षेत्रों ACCOUNTID एक int जा रहा है के लिए छोड़कर जा रहा है 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]; 

: वर्तमान में मैं का बदसूरत प्रक्रिया है। यह मुझे परेशान करता है कि वे दृढ़ता से टाइप नहीं किए जाते हैं, लेकिन अब इसे देखने के लिए देखते हैं। यह देखते हुए कि parser.ReadFields()String[] लौटाता है, क्या संरचना को भरने के लिए एक और अधिक प्रभावी तरीका है (संभवतः row[0] जैसे कुछ मानों को परिवर्तित करना int बनने की आवश्यकता है) सरणी में मानों के साथ?

** संपादित करें: ** एक प्रतिबंध मुझे लगता है कि हो सकता है प्रभाव समाधान किस तरह काम करेंगे कि इस struct [Serializable] है और कहीं और टीसीपी भेजा जाएगा उल्लेख करना भूल गया।

+1

प्रतिबिंब का उपयोग करें। – Grozz

+0

प्रतिबिंब निश्चित रूप से कम कुशल होगा, मैं बस इसके साथ रहूंगा क्योंकि – RobJohnson

+0

सीएसवीहेल्पर आपके लिए बहुत उपयोगी हो सकता है 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 मूल्य संबद्ध करने के लिए इस्तेमाल किया जा सकता।

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

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

मूल्यों फिर अपने struct प्रकार के लिए Type वस्तु के साथ सेट किया जा सकता (आप इसे प्राप्त कर सकते हैं:

उसके बाद, आप उस गुण (बस कुछ अनुकरणीय लाइनों) के साथ struct में अपने गुण को चिह्नित कर सकता है typeof ऑपरेटर के साथ): अपने struct प्रकार के सभी गुण से अधिक

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

इस कोड को दोहराता। प्रत्येक संपत्ति के लिए, यह ऊपर परिभाषित हमारे कस्टम विशेषता प्रकार के लिए जांचता है। यदि ऐसी विशेषता मौजूद है, और यदि संपत्ति का प्रकार string या int है, तो मान संबंधित सरणी अनुक्रमणिका से कॉपी किया गया है।

मैं string और int गुणों की जांच कर रहा हूं क्योंकि आपके प्रश्न में आपके द्वारा वर्णित दो डेटा प्रकार हैं। भले ही आपके पास केवल एक विशेष इंडेक्स है जिसमें int मान शामिल है, तो यह कोड स्थिरता के लिए अच्छा है यदि यह कोड स्ट्रिंग या इंट प्रॉपर्टी के रूप में किसी भी इंडेक्स को संभालने के लिए तैयार है।

ध्यान दें कि संभाल करने के लिए प्रकार की एक बड़ी संख्या के लिए, मैं if और else if की एक श्रृंखला का उपयोग नहीं कर सुझाव देंगे, बल्कि एक Dictionary<Type, Func<string, object>> कि रूपांतरण कार्यों के लिए संपत्ति प्रकार मैप करता है।

0

टिप्पणी में सुझाए गए @Grozz के रूप में प्रतिबिंब का उपयोग करें। संरचना वर्ग की प्रत्येक प्रॉपर्टी को एक विशेषता के साथ चिह्नित करें (यानी [ColumnOrdinal]) और उसके बाद उचित कॉलम के साथ जानकारी को मैप करने के लिए इसका उपयोग करें। यदि आपके पास लक्ष्य के रूप में डबल, दशमलव और इतने पर हैं, तो आपको लक्ष्य प्रकार में उचित रूप से परिवर्तित करने के लिए Convert.ChangeType का उपयोग करने पर भी विचार करना चाहिए।यदि आप प्रदर्शन से खुश नहीं हैं, तो आप फ्लाई पर DynamicMethod बनाने का आनंद ले सकते हैं, अधिक चुनौतीपूर्ण, लेकिन वास्तव में निष्पादक और सुंदर। चुनौती है कि आईएल निर्देश को स्मृति में "नलसाजी" करने के लिए स्मृति में लिखना है (मैं आमतौर पर कुछ उदाहरण कोड बना देता हूं, और उसके बाद आईएल जासूस के साथ शुरुआती बिंदु के रूप में देखता हूं)। बेशक आप कहीं ऐसी गतिशील विधियों को कैश करेंगे ताकि उन्हें एक बार अनुरोध किया जा सके।

0

पहली बात जो दिमाग में आती है वह गुणों पर पुनरावृत्ति करने के लिए प्रतिबिंब का उपयोग करना है और एक विशेषता मूल्य के आधार पर string[] में तत्वों से मेल खाता है।

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

MyAttribute सिर्फ एक सूचकांक कि सीएसवी में क्षेत्र की स्थिति से मेल होगा साथ एक कस्टम विशेषता होगी।

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 स्पष्ट रूप से प्रत्येक क्षेत्र के लिए एक स्तंभ संख्या बताए बिना, क्रम में struct क्षेत्रों रखेंगे ताकि आप उन पर पुनरावृति कर सकते हैं । यदि आपके पास बहुत सारे फ़ील्ड हैं तो इससे कुछ रखरखाव बचा सकता है।

या, आप प्रक्रिया पूरी तरह से छोड़ सकता है, और एक शब्दकोश में फ़ील्ड नाम की दुकान:

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

लोड करने के लिए आसान है कि नहीं, बल्कि बहुत मजबूत टाइपिंग, जो की तुलना में यह कम करता है का लाभ ले करता है आदर्श।

आप एक गतिशील विधि या टी 4 टेम्पलेट भी उत्पन्न कर सकते हैं। आप प्रारूप में एक मैपिंग फ़ाइल से कोड उत्पन्न कर सकता है

0,WorkOrder 
1,Description 
... 

कि लोड, और एक विधि है कि इस तरह दिखता है उत्पन्न:

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

आदि

यही दृष्टिकोण में कुछ में प्रयोग किया जाता है माइक्रो-ओआरएम चारों ओर तैरते हैं और बहुत अच्छी तरह से काम करते हैं।

आदर्श रूप से, आपके सीएसवी में फ़ील्ड नामों के साथ एक पंक्ति शामिल होगी जो इसे बहुत आसान बना देगी।

या फिर, एक और दृष्टिकोण, क्षेत्र को रखने से बचने के लिए गतिशील विधि के साथ StructLayout का उपयोग करें: कॉलम_इंडेक्स मैपिंग को स्ट्रक्चर से अलग करें।

या, आप बहुत लचीला आप एक कस्टम विशेषता का उपयोग DispatchCall पर प्रत्येक संपत्ति चिह्नित कर सकते हैं कुछ बनाना चाहते हैं एक enum

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

} 

यह आपको प्रत्येक संपत्ति को कॉलम से जोड़ने की अनुमति देता है। प्रत्येक पंक्ति के लिए आप फिर सभी गुणों पर पुन: प्रयास कर सकते हैं और विशेषता का उपयोग करके आप सही संपत्ति को सही मान निर्दिष्ट कर सकते हैं। आपको स्ट्रिंग से संख्याओं, तिथियों और शायद enums से कुछ प्रकार का रूपांतरण करना होगा। आप उस प्रक्रिया में आपकी सहायता के लिए विशेषता में अतिरिक्त गुण जोड़ सकते हैं।उदाहरण में मैं Format का आविष्कार किया है जो जब एक DateTime पार्स किया गया है इस्तेमाल किया जाना चाहिए:

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 तरीकों का उपयोग करके आप अधिक सामग्री प्रदान करने के लिए जब एक unparsable मूल्य का सामना करना पड़ा है की अनुमति देकर त्रुटि निवारण को सुधारने कर सकते हैं।

दुर्भाग्यवश, यह दृष्टिकोण बहुत प्रभावी नहीं है क्योंकि प्रतिबिंब कोड आपकी इनपुट फ़ाइल में प्रत्येक पंक्ति के लिए निष्पादित किया जाएगा। यदि आप इसे और अधिक कुशल बनाना चाहते हैं तो आपको 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 आप एक समय में एक क्षेत्र को पढ़ने के लिए अनुमति नहीं है, तो आप इमारत और स्तंभों सरणी का अनुक्रमण से बचने के सकता है।

संबंधित मुद्दे