I have a DAL which access several CSV files, that are constantly getting their formats updated, and constantly get new formats added.
each lines in the CSV file is represents a class in the system, but because of the many formats, the input is handled via a 2 step process.
step 1. Convert the line from the CSV file, into a list of values. Convert this list of values into a "raw" object.
This raw object will then be mapped into a Business logic object later, so all raw objects will be translated into business logic objects.
However, since some of the csv lines heavily lean towards generating a class with nested classes, my generic mapper, is less than useful at the moment.
The mapping object, is an attribute with the index position of the value fields assigned by the attribute on the property.
public static T BuildRawObject(string[] values)
{
T target = (T) Activator.CreateInstance(typeof(T), new object[] { });
Type targetInfo = target.GetType();
foreach (PropertyInfo pi in targetInfo.GetProperties())
{
if (!pi.PropertyType.IsValueType)
{
if (pi.PropertyType.IsAssignableFrom(typeof(NestedObj1)))
{
var result = BuildRawObject2<NestedObj1>(values);
try
{
pi.SetValue(target, Convert.ChangeType(result, pi.PropertyType));
}
catch (Exception e)
{
Console.WriteLine("Exception on property: " + pi.PropertyType +
" was bit not to set built instance on property. Instace was: " + result);
Console.WriteLine("Exception mesessage was: " + e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner Exception message was: " + e.InnerException.Message);
}
}
}
else if(pi.PropertyType.IsAssignableFrom(typeof(NestedObj2)))
{
var result = BuildRawObject2<NestedObj2>(values);
try
{
pi.SetValue(target, Convert.ChangeType(result, pi.PropertyType));
}
catch (Exception e)
{
Console.WriteLine("Exception on property: " + pi.PropertyType +
" was bit not to set built instance on property. Instace was: " + result);
Console.WriteLine("Exception mesessage was: " + e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner Exception message was: " + e.InnerException.Message);
}
}
}
else if (pi.PropertyType.IsAssignableFrom(typeof(NestedObj3)))
{
var result = BuildRawObject2<NestedObj3>(values);
try
{
pi.SetValue(target, Convert.ChangeType(result, pi.PropertyType));
}
catch (Exception e)
{
Console.WriteLine("Exception on property: " + pi.PropertyType +
" was bit not to set built instance on property. Instace was: " + result);
Console.WriteLine("Exception mesessage was: " + e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner Exception message was: " + e.InnerException.Message);
}
}
}
else if (pi.PropertyType.IsAssignableFrom(typeof(NestedObj5)))
{
var result = BuildRawObject2<NestedObj5>(values);
try
{
pi.SetValue(target, Convert.ChangeType(result, pi.PropertyType));
}
catch (Exception e)
{
Console.WriteLine("Exception on property: " + pi.PropertyType +
" was bit not to set built instance on property. Instace was: " + result);
Console.WriteLine("Exception mesessage was: " + e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner Exception message was: " + e.InnerException.Message);
}
}
}
else if (pi.PropertyType.IsAssignableFrom(typeof(NestedObj6)))
{
var result = BuildRawObject2<NestedObj6>(values);
try
{
pi.SetValue(target, result);
}
catch (Exception e)
{
Console.WriteLine("Exception on property: " + pi.PropertyType +
" was bit not to set built instance on property. Instace was: " + result);
Console.WriteLine("Exception mesessage was: " + e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner Exception message was: " + e.InnerException.Message);
}
}
}
else if (pi.PropertyType.IsAssignableFrom(typeof(NestedObj7)))
{
var result = BuildRawObject2<NestedObj7>(values);
try
{
pi.SetValue(target, Convert.ChangeType(result, pi.PropertyType));
}
catch (Exception e)
{
Console.WriteLine("Exception on property: " + pi.PropertyType +
" was bit not to set built instance on property. Instace was: " + result);
Console.WriteLine("Exception mesessage was: " + e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner Exception message was: " + e.InnerException.Message);
}
}
}
else
{
Console.WriteLine("An unrecognized class exists in the raw object. This will not be mapped to: " + pi.Name);
}
}
else
{
Mapping mapping = pi.GetCustomAttribute<Mapping>();
if (mapping != null)
{
values[mapping.SourceIndex] = EmptyStringValuesWhenEmpty(values[mapping.SourceIndex]);
if (pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(DateTime?))
{
bool valid = DateTime.TryParse(values[mapping.SourceIndex], out DateTime dateTime);
if (valid)
{
pi.SetValue(target, dateTime, null);
}
else
{
Console.WriteLine("Value: " + values[mapping.SourceIndex] + " on position[" +
mapping.SourceIndex + "] was not a valid date time. Value is required.");
}
}
else
{
try
{
pi.SetValue(target, GenericConverter(values[mapping.SourceIndex], pi.PropertyType), null);
}
catch (Exception e)
{
Console.WriteLine("Exception on index: " + mapping.SourceIndex + " contained value: " +
values[mapping.SourceIndex] + " was attempted for property type: " +
pi.PropertyType +
"Parse was not possible. Either input file is in wrong format, or RawInputObject has incorect property");
Console.WriteLine("Exception mesessage was: " + e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner Exception message was: " + e.InnerException.Message);
}
}
}
}
else
{
Console.WriteLine("Mapping to field: " + pi.Name +
" was skipped due to missing mapping attribute definition.");
}
}
}
return target;
}
The trouble comes when I try to assign values to nested classes for the "Raw return object".
ideally I would have prefered to make the whole thing a recursive method. However the parent class is not the same type as the nested classes. So I have to generate a new method, that does the same as the parent class but accepts the nested classes
However, generating an, in theory, infinite list of named type objects that might occur within a generic method is just ... wrong. I would prefer to simply let the program infer from whatever specialized nested class is handed to the method to determine what the return type of the method would be. Is this possible? I can't figure out how.
If I am not clear in my question, or you have suggestions on how I could ask the question better, please let me know. If details are lacking. Please advice, and I shall provide.
Edit: Method after solution was provided
public static object BuildRawObject(Type objectType, string[] values)
{
dynamic target = Activator.CreateInstance(objectType, new object[] { });
Type targetInfo = target.GetType();
foreach (PropertyInfo pi in targetInfo.GetProperties())
{
if (pi.PropertyType.GetInterfaces().AsEnumerable().Contains(typeof(IRawObjectBase)) || pi.PropertyType.GetInterfaces().AsEnumerable().Contains(typeof(IRawNestedObjectBase)))
{
var result = BuildRawObject(pi.PropertyType, values);
try
{
pi.SetValue(target, Convert.ChangeType(result, pi.PropertyType));
}
catch (Exception e)
{
Console.WriteLine("Exception on property: " + pi.PropertyType +
" was bit not to set built instance on property. Instace was: " + result);
Console.WriteLine("Exception mesessage was: " + e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner Exception message was: " + e.InnerException.Message);
}
}
}
else
{
Mapping mapping = pi.GetCustomAttribute<Mapping>();
if (mapping != null)
{
values[mapping.SourceIndex] = EmptyStringValuesWhenEmpty(values[mapping.SourceIndex]);
if (pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(DateTime?))
{
bool valid = DateTime.TryParse(values[mapping.SourceIndex], out DateTime dateTime);
if (valid)
{
pi.SetValue(target, dateTime, null);
}
else
{
Console.WriteLine("Value: " + values[mapping.SourceIndex] + " on position[" +
mapping.SourceIndex + "] was not a valid date time. Value is required.");
}
}
else
{
try
{
pi.SetValue(target, GenericConverter(values[mapping.SourceIndex], pi.PropertyType), null);
}
catch (Exception e)
{
Console.WriteLine("Exception on index: " + mapping.SourceIndex + " contained value: " +
values[mapping.SourceIndex] + " was attempted for property type: " +
pi.PropertyType +
"Parse was not possible. Either input file is in wrong format, or RawInputObject has incorect property");
Console.WriteLine("Exception mesessage was: " + e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner Exception message was: " + e.InnerException.Message);
}
}
}
}
else
{
Console.WriteLine("Mapping to field: " + pi.Name +
" was skipped due to missing mapping attribute definition.");
}
}
}
return target;
}
It seems that unnecessary usage of generics bites you in this case. If you change your signature to
static object BuildRawObject(Type objectType, string[] values)
you will be able to pass property type directly there, without a lot of checks for property type:
if (propertyIsNotPrimitiveType) {
var result = BuildRawObject(pi.PropertyType, values);
}
For convinience you can still leave your generic overload:
static T BuildRawObject<T>(string[] values) {
return (T) BuildRawObject(typeof(T), values);
}