带有 non-default 构造函数的 CsvHelper
CsvHelper with non-default constructor
我的问题如下。我想借助 WPF c# 下的 CsvHelper 将 class 属性写入 CSV 文件,然后再读回它们。引用的 classes 之一有一个 non-default 参数化构造函数。如何让 CsvHelper 正确调用其构造函数?
因此我使用了下面的地图文件。
public class TradeLogRecMap : ClassMap<TradeLogRec>
{
public TradeLogRecMap()
{
AutoMap(CSVConfig);
}
}
有了这些 classes 和结构
public class Liquidity
{
static readonly Dictionary<int, string> Values = new Dictionary<int, string>
{
{0, "None"},
{1, "Added Liquidity"},
{2, "Removed Liquidity"},
{3, "Liquidity Routed Out" }
};
public Liquidity(int p)
{
Value = Values.ContainsKey(p) ? p : 0;
}
public int Value { get; set; }
public override string ToString()
{
return Values[Value];
}
}
public class Execution
{
public Liquidity LastLiquidity { get; set; }
public Execution()
{
LastLiquidity = new Liquidity(0);
}
}
public struct TradeLogRec
{
public Execution execution { set; get; }
}
当我写 CSV 文件时看起来没问题。但是读回来它说它错过了 header “p”。在 CSV 文件中添加“p”header 可以解决这个问题,但这样做不是一种选择。同时添加默认构造函数
public Liquidity()
{
}
问题解决。但这不是以太币的选择。
为什么它无论如何都需要 header“p”?
是否可以通过映射文件或CvsHelper配置中的一些构造函数设置来解决?
还是bug?
从 CsvHelper 27.1.1 开始,当 CsvHelper 在读取期间尝试构造一个 class 时,它将使用无参数构造函数(如果存在)。如果 none 存在,它将使用带有 most parameters [1] 的参数化构造函数。调用参数化构造函数时,CSV 列通过参数名称 的精确匹配与构造函数参数 匹配。在您的 Liquidity
class 中,您要映射到“值”列的参数名为 p
,因此无法进行匹配。
那么,你有什么选择?
首先,如果您可以修改 Liquidity
class,则可以将 p
构造函数参数重命名为 Value
:
public Liquidity(int Value)
{
// Make the constructor parameter name match the property name exactly for CsvHelper
this.Value = Values.ContainsKey(Value) ? Value : 0;
}
这样做之后,一切都会正常进行。演示 fiddle #1 here.
其次,您可以修改 Liquidity
以将 [CsvHelper.Configuration.Attributes.Name("Value")]
添加到 p
:
public Liquidity([CsvHelper.Configuration.Attributes.Name("Value")] int p)
{
Value = Values.ContainsKey(p) ? p : 0;
}
演示 fiddle #2 here.
第三,如果您不能以任何方式修改您的 classes,您可以覆盖 AutoMap
生成的默认引用映射并为 Liquidity
提供您自己的映射。
创建以下 ClassMap<Liquidity>
:
public class LiquidityMap : ClassMap<Liquidity>
{
public LiquidityMap()
{
Map(m => m.Value);
Parameter("p").Name(nameof(Liquidity.Value));
}
}
并修改TradeLogRecMap
使用如下:
public class TradeLogRecMap : ClassMap<TradeLogRec>
{
public TradeLogRecMap()
{
// Automap TradeLogRecMap
AutoMap(CSVConfig); // CSVConfig was not shown in your question, I used new CsvConfiguration(CultureInfo.InvariantCulture) { }
// Get the reference map for Execution
var executionRefMap = ReferenceMaps.Find<TradeLogRec>(m => m.execution);
// Get its reference map for LastLiquidity
var liquidityRefMap = executionRefMap.Data.Mapping.ReferenceMaps.Find<Execution>(m => m.LastLiquidity);
// Remove the auto-generated reference map for LastLiquidity
executionRefMap.Data.Mapping.ReferenceMaps.Remove(liquidityRefMap);
// And add a reference map for LastLiquidity using LiquidityMap
executionRefMap.Data.Mapping.ReferenceMaps.Add(new MemberReferenceMap(liquidityRefMap.Data.Member, new LiquidityMap()));
}
}
演示 fiddle #3 here.
最后,您可以修改 CsvConfiguration.PrepareHeaderForMatch
以自动将任何名为“p”的列重新映射到“Value”:
public static CsvConfiguration CSVConfig =>
new CsvConfiguration(CultureInfo.InvariantCulture)
{
PrepareHeaderForMatch = a => a.Header == "p" ? nameof(Liquidity.Value) : a.Header,
};
然后在构造你的CsvReader
时使用它:
public static List<TradeLogRec> DeserializeTradeLogRecMapFromCsv(TextReader reader)
{
using (var csv = new CsvReader(reader, CSVConfig))
{
csv.Context.RegisterClassMap<TradeLogRecMap>();
return csv.GetRecords<TradeLogRec>().ToList();
}
}
这似乎有点笨拙,因为它将 所有 名为“Value”的列映射到“p”,而不仅仅是 Liquidity
的列 - 但它确实有效。演示 fiddle #4 here.
[1] 此行为是通过 CsvConfiguration.ShouldUseConstructorParameter
and CsvConfiguration.GetConstructor
.
配置的
我的问题如下。我想借助 WPF c# 下的 CsvHelper 将 class 属性写入 CSV 文件,然后再读回它们。引用的 classes 之一有一个 non-default 参数化构造函数。如何让 CsvHelper 正确调用其构造函数?
因此我使用了下面的地图文件。
public class TradeLogRecMap : ClassMap<TradeLogRec>
{
public TradeLogRecMap()
{
AutoMap(CSVConfig);
}
}
有了这些 classes 和结构
public class Liquidity
{
static readonly Dictionary<int, string> Values = new Dictionary<int, string>
{
{0, "None"},
{1, "Added Liquidity"},
{2, "Removed Liquidity"},
{3, "Liquidity Routed Out" }
};
public Liquidity(int p)
{
Value = Values.ContainsKey(p) ? p : 0;
}
public int Value { get; set; }
public override string ToString()
{
return Values[Value];
}
}
public class Execution
{
public Liquidity LastLiquidity { get; set; }
public Execution()
{
LastLiquidity = new Liquidity(0);
}
}
public struct TradeLogRec
{
public Execution execution { set; get; }
}
当我写 CSV 文件时看起来没问题。但是读回来它说它错过了 header “p”。在 CSV 文件中添加“p”header 可以解决这个问题,但这样做不是一种选择。同时添加默认构造函数
public Liquidity()
{
}
问题解决。但这不是以太币的选择。
为什么它无论如何都需要 header“p”?
是否可以通过映射文件或CvsHelper配置中的一些构造函数设置来解决?
还是bug?
从 CsvHelper 27.1.1 开始,当 CsvHelper 在读取期间尝试构造一个 class 时,它将使用无参数构造函数(如果存在)。如果 none 存在,它将使用带有 most parameters [1] 的参数化构造函数。调用参数化构造函数时,CSV 列通过参数名称 的精确匹配与构造函数参数 匹配。在您的 Liquidity
class 中,您要映射到“值”列的参数名为 p
,因此无法进行匹配。
那么,你有什么选择?
首先,如果您可以修改 Liquidity
class,则可以将 p
构造函数参数重命名为 Value
:
public Liquidity(int Value)
{
// Make the constructor parameter name match the property name exactly for CsvHelper
this.Value = Values.ContainsKey(Value) ? Value : 0;
}
这样做之后,一切都会正常进行。演示 fiddle #1 here.
其次,您可以修改 Liquidity
以将 [CsvHelper.Configuration.Attributes.Name("Value")]
添加到 p
:
public Liquidity([CsvHelper.Configuration.Attributes.Name("Value")] int p)
{
Value = Values.ContainsKey(p) ? p : 0;
}
演示 fiddle #2 here.
第三,如果您不能以任何方式修改您的 classes,您可以覆盖 AutoMap
生成的默认引用映射并为 Liquidity
提供您自己的映射。
创建以下 ClassMap<Liquidity>
:
public class LiquidityMap : ClassMap<Liquidity>
{
public LiquidityMap()
{
Map(m => m.Value);
Parameter("p").Name(nameof(Liquidity.Value));
}
}
并修改TradeLogRecMap
使用如下:
public class TradeLogRecMap : ClassMap<TradeLogRec>
{
public TradeLogRecMap()
{
// Automap TradeLogRecMap
AutoMap(CSVConfig); // CSVConfig was not shown in your question, I used new CsvConfiguration(CultureInfo.InvariantCulture) { }
// Get the reference map for Execution
var executionRefMap = ReferenceMaps.Find<TradeLogRec>(m => m.execution);
// Get its reference map for LastLiquidity
var liquidityRefMap = executionRefMap.Data.Mapping.ReferenceMaps.Find<Execution>(m => m.LastLiquidity);
// Remove the auto-generated reference map for LastLiquidity
executionRefMap.Data.Mapping.ReferenceMaps.Remove(liquidityRefMap);
// And add a reference map for LastLiquidity using LiquidityMap
executionRefMap.Data.Mapping.ReferenceMaps.Add(new MemberReferenceMap(liquidityRefMap.Data.Member, new LiquidityMap()));
}
}
演示 fiddle #3 here.
最后,您可以修改 CsvConfiguration.PrepareHeaderForMatch
以自动将任何名为“p”的列重新映射到“Value”:
public static CsvConfiguration CSVConfig =>
new CsvConfiguration(CultureInfo.InvariantCulture)
{
PrepareHeaderForMatch = a => a.Header == "p" ? nameof(Liquidity.Value) : a.Header,
};
然后在构造你的CsvReader
时使用它:
public static List<TradeLogRec> DeserializeTradeLogRecMapFromCsv(TextReader reader)
{
using (var csv = new CsvReader(reader, CSVConfig))
{
csv.Context.RegisterClassMap<TradeLogRecMap>();
return csv.GetRecords<TradeLogRec>().ToList();
}
}
这似乎有点笨拙,因为它将 所有 名为“Value”的列映射到“p”,而不仅仅是 Liquidity
的列 - 但它确实有效。演示 fiddle #4 here.
[1] 此行为是通过 CsvConfiguration.ShouldUseConstructorParameter
and CsvConfiguration.GetConstructor
.