6

CSV ファイルを解析し、データを構造体に配置しています。この質問の を使用しTextFieldParserていますが、 を返すことを除いて、魅力のように機能しています。現在、私は醜いプロセスを持っています: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];

String構造体は、である AccountID を除いて、であるすべてのフィールドで構成されintます。それらが強く型付けされていないのは私を悩ませますが、今のところそれを見てみましょう. parser.ReadFields()a を返すことを考えると、配列内の値で構造体を埋める (おそらく、になる必要があるString[]などのいくつかの値を変換する) より効率的な方法はありますか?row[0]int

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

4

6 に答える 6

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

intつまり、属性を使用して、名前付きの値をプロパティに関連付けることができIndexます。

次に、構造体のプロパティをその属性でマークすることができます (いくつかの典型的な行):

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

このコードは、構造体型のすべてのプロパティを反復処理します。プロパティごとに、上で定義したカスタム属性タイプをチェックします。そのような属性が存在し、プロパティ タイプがstringまたはintの場合、値はそれぞれの配列インデックスからコピーされます。

string質問で言及した2つのデータ型であるため、intプロパティをチェックしています。int現在、値を含む特定のインデックスが 1 つしかない場合でも、このコードが任意のインデックスを文字列または int プロパティとして処理できるように準備されていると、保守性が向上します。

ifより多くの型を処理するには、 andのチェーンを使用するのでelse ifはなく、Dictionary<Type, Func<string, object>>プロパティ型を変換関数にマップする を使用することをお勧めします。

于 2012-08-17T16:00:04.350 に答える
1

非常に柔軟なものを作成したい場合はDispatchCall、カスタム属性を使用して各プロパティをマークできます。このようなもの:

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

}

これにより、各プロパティを列に関連付けることができます。行ごとに、すべてのプロパティを反復処理し、属性を使用して適切な値を適切なプロパティに割り当てることができます。文字列から数値、日付、そしておそらく列挙型への型変換を行う必要があります。そのプロセスを支援するために、属性に追加のプロパティを追加できます。私が発明した例では、 aが解析さ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すると、解析できない値が検出されたときに、より多くのコンテキストを提供できるようになるため、エラー処理が改善されます。

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

于 2012-08-17T16:11:31.873 に答える
1

使用しているライブラリにどの程度依存していますか? 私は、ファイル ヘルパーがこの種の作業に非常に役立つことを発見しました。コードは次のようになります。

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 配列が作成され、エンジンが面倒な作業をすべて行ってくれました。

于 2012-08-17T16:25:49.247 に答える
0

最初に頭に浮かぶのは、リフレクションを使用してプロパティを反復処理しstring[]、属性値に基づいてプロパティをの要素に一致させることです。

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

MyAttributeCSVのフィールド位置に一致するインデックスを持つカスタム属性になります。

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

このアプローチは、浮かんでいるいくつかのマイクロORMで使用されており、かなりうまく機能しているようです。

理想的には、CSVには、これをはるかに簡単にするフィールド名の行が含まれます。

または、さらに別のアプローチとしてStructLayout、動的メソッドと一緒に使用して、構造体自体とは別にfield:column_indexマッピングを保持する必要をなくします。

または、列挙型を作成します

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.
}
于 2012-08-17T16:07:24.610 に答える
0

あなたがスピードを求めているなら、あなたは壊れやすいswitchステートメントかもしれません。

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一度に1つのフィールドを読み取ることができないのは残念です。そうすれば、列配列の作成とインデックス作成を回避できます。

于 2012-08-17T16:46:20.100 に答える
0

コメントで @Grozz が提案したように、リフレクションを使用します。構造体クラスの各プロパティを属性 (つまり[ColumnOrdinal]) でマークし、これを使用して情報を適切な列にマップします。double、decimal などを対象としている場合Convert.ChangeTypeは、対象の型で適切な convert を使用することも検討する必要があります。パフォーマンスに満足できない場合は、DynamicMethodをオンザフライで作成して楽しむことができます。これは、より困難ですが、非常にパフォーマンスが高く美しいものです。課題は、手動で行った「配管」を行うためにメモリに IL 命令を書き込むことです (通常、いくつかのサンプル コードを作成し、IL スパイを出発点としてその内部を調べます)。もちろん、そのような動的メソッドをどこかにキャッシュするので、それらの作成は一度だけ要求されます。

于 2012-08-17T16:01:24.680 に答える