【发布时间】:2019-12-23 18:13:41
【问题描述】:
我正在使用出色的 CsvHelper 库(当前为 v12.2.2)生成 CSV 文件,并且我正在尝试添加自己的自定义属性以直接在类中指定特殊格式。
我正在写的记录看起来像这样(尽管集成需要大约 200 个数字字段):
class PayrollRecord {
public int EmployeeID { get; set; }
public decimal RegularPay { get; set; }
public decimal RegularHours { get; set; }
public decimal RegularRate { get; set; }
public decimal OvertimePay { get; set; }
public decimal OvertimeHours { get; set; }
public decimal OvertimeRate { get; set; }
// many many more
}
并且我需要确保 Pay 用小数点后 2 位写入,小时用 3 表示,并且工资率用 4 表示;集成需要这个。
现在有效的方法
我创建了一个十进制转换器,并将其附加到类映射:
using CsvHelper;
using CsvHelper.TypeConversion;
// convert decimal to the given number of places, and zeros are
// emitted as blank.
public abstract class MyDecimalConverter : DefaultTypeConverter
{
protected virtual string getFormat() => "";
public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
{
if (value is decimal d)
return (d == 0) ? string.Empty : string.Format(getFormat(), d);
return base.ConvertToString(value, row, memberMapData);
}
}
public class DollarsConverter : MyDecimalConverter
{
protected override string getFormat() => "{0:0.00}"; // 2 decimal places
}
public class HoursConverter : MyDecimalConverter
{
protected override string getFormat() => "{0:0.000}"; // 3 decimal places
}
public class PayRateConverter : MyDecimalConverter
{
protected override string getFormat() => "{0:0.0000}"; // 4 decimal places
}
然后我在创建作家时应用这些:
CsvWriter Writer = new CsvWriter( /* stuff */ );
var classMap = new DefaultClassMap<PayrollRecord>();
classMap.AutoMap();
classMap.Map(m => m.RegularPay).TypeConverter<DollarsConverter>();
classMap.Map(m => m.RegularHours).TypeConverter<HoursConverter>();
classMap.Map(m => m.RegularRate).TypeConverter<PayRateConverter>();
classMap.Map(m => m.OvertimePay).TypeConverter<DollarsConverter>();
classMap.Map(m => m.OvertimeHours).TypeConverter<HoursConverter>();
classMap.Map(m => m.OvertimeRate).TypeConverter<PayRateConverter>();
// many more
Writer.Configuration.RegisterClassMap(classMap);
...
这一切都正确,但是它不能很好地扩展:有大约 200 个字段,使映射内容与实际字段定义保持同步将是一个挑战,我非常喜欢在我们确定集成之前,预计记录结构会发生变化。
旁注:可以使用[Format("..")] 属性注释每个字段,但是为了获得我正在寻找的零抑制,格式字符串是一个由三部分组成的丑陋的东西,看起来超级容易出错并且非常改变很乏味。
我想要什么
我想创建自己的自定义属性,我可以将其应用于每个字段成员来指定它,所以它看起来像:
// custom attribute
public enum NumericType { Dollars, Hours, PayRate };
public class DecimalFormatAttribute : System.Attribute
{
public NumericType Type { get; }
public DecimalFormatAttribute(NumericType t) => Type = t;
}
// then later
class PayrollRecord {
[DecimalFormat(NumericType.Dollars)] public decimal RegularPay { get; set; }
[DecimalFormat(NumericType.Hours)] public decimal RegularHours { get; set; }
[DecimalFormat(NumericType.PayRate)] public decimal RegularRate { get; set; }
// etc.
}
我遇到的困难是如何将我的自定义属性粘合到类映射上,我认为代码看起来像这样:
var classMap = new DefaultClassMap<PayrollRecord>();
classMap.AutoMap();
foreach (var prop in typeof(PayrollRecord).GetProperties())
{
var myattr = (DecimalFormatAttribute)prop.GetCustomAttribute(typeof(DecimalFormatAttribute));
if (myattr != null)
{
// prop.Name is the base name of the field
// WHAT GOES HERE?
}
}
我已经为此苦苦琢磨了几个小时,但不知道如何完成。
【问题讨论】:
-
你为什么在乎写什么?您不想在输出时格式化吗?输入数据时1.0和1.0000有什么区别?
-
1.0 和 1.0000 之间显然没有区别,但工资率 15.4837/hr 和 15.48/hr 之间肯定有区别。
-
... 在这种情况下,我可能会为所有内容保留四位小数(使其变得简单),尽管我不确定集成的接收者是否会接受这种方式,但我仍然想弄清楚如何为无法简单解决的情况附加我自己的属性。
-
然后以文本形式输入数据并使用正则表达式确保格式正确。然后转换为数字。
-
而不是您自己的自定义属性,为什么不将标准属性
CsvHelper.Configuration.Attributes.TypeConverterAttribute应用于您的模型?见dotnetfiddle.net/m2nORw