这里的总体目标是这样的:我们在Azure blob存储中存储了许多不同名称和格式的CSV文件。我们需要将它们转换为列表。
我有一个界面:
B
然后这是实现它的类的示例:
public interface IGpasData
{
List<T> ConvertToList<T>(StreamReader reader);
}
为继续上述类的示例,有一个名为ToC.csv的文件。在旨在将THAT文件转换为列表的类中,我进行了以下调用:
public class GpasTableOfContent : IGpasData
{
public string TocProp0 { get; set; }
public string TocProp1 { get; set; }
public string TocProp2 { get; set; }
public List<T> ConvertToList<T>(StreamReader reader)
{
List<T> dataList = new List<T>();
while (!reader.EndOfStream)
{
var lineItem = reader.ReadLine();
GpasTableOfContent dataItem = new GpasTableOfContent
{
TocProp0 = lineItem.Split(',')[0],
TocProp1 = lineItem.Split(',')[1],
Type = lineItem.Split(',')[2]
};
dataList.Add(dataItem);
}
return dataList;
}
}
其他一些可能的示例:
List<GpasTableOfContent> gpasToCList = ConvertCloudFileToList<GpasTableOfContent>("ToC.csv", "MyModel");
这是ConvertCloudFileToList:
List<GpasFoo> gpasFooList = ConvertCloudFileToList<GpasFoo>("foo.csv", "MyModel");
List<GpasBar> gpasBarList = ConvertCloudFileToList<GpasBar>("bar.csv", "MyModel");
这使我们回到private List<T> ConvertCloudFileToList<T>(string fileName, string modelName)
{
// Get the .csv file from the InProgress Directory
string filePath = $"{modelName}/{fileName}";
CloudFile cloudFile = _inProgressDir.GetFileReference(filePath);
List<T> dataList = new List<T>();
// Does the file exist?
if (!cloudFile.Exists())
return dataList;
using (StreamReader reader = new StreamReader(cloudFile.OpenRead()))
{
IGpasData gpasData = (IGpasData)Activator.CreateInstance<T>();
dataList = gpasData.ConvertToList<T>(reader);
}
return dataList;
}
。问题在这里:
ConvertToList
无法将'GpasFoo'转换为'T'
不确定如何解决此问题。
答案 0 :(得分:1)
如果不提供一些其他逻辑,那么您将无法做您想做的事情。问题是您从读取CSV文件中获得了一个字符串,并且想要将其转换为T,但是没有将字符串转换为任意类型的规则。
一种方法是将方法更改为也使用一个委托Func,该委托Func用于将每行转换为T。例如,如果确保您的数据包含双精度数,则可以传递t => Double .Parse(t)表示该参数。当然,这种方法要求您更改要实现的接口方法的签名。
如果您无法更改接口方法的签名,那么我只建议尝试处理一组预定义的类型并为其他类型抛出异常。
答案 1 :(得分:1)
正如其他人指出的那样,这种设计有缺陷:
public interface IGpasData { List<T> ConvertToList<T>(StreamReader reader); }
该合同规定IGpasData
仅应知道如何反序列化任何内容。这没有道理。
IGpasData
应该知道如何反序列化自身,为此,我们需要一个自引用接口:
public interface IGpasData<T> where T : IGpasData<T>
{
List<T> ConvertToList(StreamReader reader);
}
public class GpasBar: IGpasData<GpasBar>
{
public string MyPropertyA { get; set; }
public int MyPropertyB { get; set; }
public List<GpasBar> ConvertToList(StreamReader reader)
{
var results = new List<GpasBar>();
while (!reader.EndOfStream)
{
var values = reader.ReadLine().Split(',');
results.Add(new GpasBar()
{
PropertyA = values[0],
PropertyB = int.Parse(values[1]),
});
}
return results;
}
}
或者,IGpasData
应该知道如何从值数组中填充自身:
public interface IGpasData
{
void Populate(string[] values);
}
public class GpasBar
{
public string MyPropertyA { get; set; }
public int MyPropertyB { get; set; }
public void Populate(string[] values)
{
MyPropertyA = values[0];
MyPropertyB = int.Parse(values[1]);
}
}
public static List<T> ConvertCloudFileToList<T>(string fileName, string modelName)
where T : IGpasData, new()
{
// ...
using (StreamReader reader = new StreamReader(cloudFile.OpenRead()))
{
var results = new List<T>();
while (!reader.EndOfStream)
{
var item = new T();
item.Populate(reader.ReadLine().Split(','));
results.Add(item);
}
return results;
}
}
使用第二种方法,可以避免重复关于StreamReader
的部分并读取行。
答案 2 :(得分:1)
任何IGpasData
对象都可以在提供StreamReader
时生成任何给定类型的列表。 GpasTableOfContent
无法满足此要求,它只能产生自己类型的列表。
但是,让一种类型的GpasData负责转换所有内容似乎并不合理,因此我建议将Type参数从ConvertToList
方法移到接口中。这样子类将仅负责转换特定类型的列表。
public interface IGpasData<T>
{
List<T> ConvertToList(StreamReader reader);
}
public class GpasTableOfContent : IGpasData<GpasTableOfContent>
{
//...
public List<GpasTableOfContent> ConvertToList(StreamReader reader)
{
//...
}
}
另一方面,创建一个空目录然后使用它从流中读取并生成真实目录列表对我来说似乎很笨拙。我认为,创建这些内容对象的行为应移到其自己的类中。