Let's say I have the following interface:
public interface IStateInformation
{
public int TaskId { get; set; }
public string Name { get; set; }
}
and the following class implementing this interface:
public class ExternalStateInformation : IStateInformation
{
public int TaskId { get; set; }
public string Name { get; set; }
public string External { get; set; }
}
Now I want to write the following class to a CSV:
public class SwitchingData
{
public string DateTime { get; set; }
public int EpisodeId { get; set; }
public IStateInformation SourceState { get; set; }
public IStateInformation TargetState { get; set; }
}
As you can see, this class includes IStateInformation
objects. When I try to write a record to a CSV file, only the properties of IStateInformation
are considered (TaskId
and Name
) even when e.g. SourceState
is of type ExternalStateInformation
(therefore External
is missing). My question is, how can I write the properties of a dynamic type instead of the static one?
I tried to use ClassMap
but without success.
Details
The writing script looks like this:
...
using (var stream = File.Open(path, FileMode.Append))
using (var writer = new StreamWriter(stream))
using (var csv = new CsvWriter(writer, config))
{
if(type != null)
{
csv.Context.RegisterClassMap(type);
}
csv.Context.TypeConverterOptionsCache.AddOptions<DateTime>(options);
csv.Context.TypeConverterOptionsCache.AddOptions<DateTime?>(options);
csv.WriteRecords(data);
}
...
where type
was the ClassMap
I played with.
In the end I have written my own functions writing the header and the records. My given example would lead to the following header:
DateTime,EpisodeId,SourceState_TaskId,SourceState_Name,SourceState_External,TargetState_TaskId,TargetState_Name,TargetState_External
If TargetState
would implement the IStateInformation
interface with another additional property, let's say Internal
, the header would look like:
DateTime,EpisodeId,SourceState_TaskId,SourceState_Name,SourceState_External,TargetState_TaskId,TargetState_Name,TargetState_External,TargetState_Internal
General Writing Function
private static void WriteToCSV<T>(string path, TypeConverterOptions options, List<T> data, FileMode mode)
{
using (var stream = File.Open(path, mode))
using (var writer = new StreamWriter(stream))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.Context.TypeConverterOptionsCache.AddOptions<DateTime>(options);
csv.Context.TypeConverterOptionsCache.AddOptions<DateTime?>(options);
List<Type> structure = GetStructureOfCSV(csv, data);
if (mode == FileMode.Create)
{
WriteHeaderToCSV(csv, data);
csv.NextRecord();
}
foreach (var record in data)
{
WriteRecordToCSV(csv, record, structure);
csv.NextRecord();
}
}
if (mode == FileMode.Create)
{
Debug.Log(String.Format("Write data to new file {0}", path));
}
else if (mode == FileMode.Append)
{
Debug.Log(String.Format("Write data to existing file {0}", path));
}
}
Function to get the Structure of the Object
This is needed to write default values instead of null values which would lead to too less CSV fields.
private static List<Type> GetStructureOfCSV<T>(CsvWriter csv, List<T> data)
{
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
MemberInfo[] members = data[0].GetType().GetMemberInfos(bindingFlags);
List<Type> structure = new List<Type>
{
data[0].GetType()
};
foreach (var thisVar in members)
{
if (!HasCSVHelperBuiltInConverter(thisVar.GetUnderlyingType()))
{
try
{
List<T> records = data.Where(x => thisVar.GetValue(x) != null).ToList();
List<object> values = records.Select(x => thisVar.GetValue(x)).ToList();
structure = structure.Concat(GetStructureOfCSV(csv, values)).ToList();
}
catch (InvalidOperationException)
{
}
}
}
return structure;
}
Check for ReferenceConverter
private static bool HasCSVHelperBuiltInConverter(Type type)
{
return TypeDescriptor.GetConverter(type).GetType() != typeof(ReferenceConverter);
}
Header
private static void WriteHeaderToCSV<T>(CsvWriter csv, List<T> data, string prefix = "")
{
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
MemberInfo[] members = data[0].GetType().GetMemberInfos(bindingFlags);
foreach (var thisVar in members)
{
if (!HasCSVHelperBuiltInConverter(thisVar.GetUnderlyingType()))
{
try
{
List<T> records = data.Where(x => thisVar.GetValue(x) != null).ToList();
List<object> values = records.Select(x => thisVar.GetValue(x)).ToList();
WriteHeaderToCSV(csv, values, thisVar.Name);
}
catch (InvalidOperationException)
{
csv.WriteField(thisVar.Name);
}
}
else
{
if (prefix != "" && prefix != null)
{
csv.WriteField(string.Format("{0}_{1}", prefix, thisVar.Name));
}
else
{
csv.WriteField(thisVar.Name);
}
}
}
}
Records
private static void WriteRecordToCSV(CsvWriter csv, object record, List<Type> structure)
{
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
MemberInfo[] members = record.GetType().GetMemberInfos(bindingFlags);
List<Type> local_structure = new List<Type>(structure);
local_structure.RemoveAt(0);
foreach (var thisVar in members)
{
if (!HasCSVHelperBuiltInConverter(thisVar.GetUnderlyingType()))
{
if(thisVar.GetValue(record) != null)
{
WriteRecordToCSV(csv, thisVar.GetValue(record), local_structure);
}
else
{
object instance = Activator.CreateInstance(local_structure.First());
WriteRecordToCSV(csv, instance, local_structure);
}
}
else
{
csv.WriteField(thisVar.GetValue(record));
}
}
}