In my C# application I have a class that acts as a data structure to hold various categories, which may hold sub-categories or lists of categories, which eventually hold data properties that hold values and other associated data.
The DataStructure
class is generic, because it is used in multiple places in the application either directly or as a base class for extensions, and the type of values in DataProperty
will be different in each use case. At some point I need to transfer values from instance of DataStructure
of one type to instance of DataStructure
of another type. For example, if first DataStructure
was of type string
, and the second was of type double
, the converter will cast value of each DataProperty
through some custom logic. Any lists of categories also need to be filled in the second DataStructure
, so that everything matches the first DataStructure
.
So essentially I need to make a copy of instance of DataStructure
but with another type, converting values with a custom converter. This also needs to work if the second data structure is an instance of a class that derives from DataStructure
. This is the simplified version of the code that shows the setup:
static void Main(string[] args)
{
DataStructure<string> dataStructureStr = new();
FillData(dataStructureStr); //Fills lists of categories, writes some string type values into DataProperty properties
DataStructure<double> dataStructureDbl = new();
ConvertAndCopyData(dataStructureStr, dataStructureDbl); //Transfers values of all properties from one DataStructure to another using a converter
}
public interface ICategory
{
//...
}
public abstract class Category : ICategory
{
//...
}
public class DataProperty<T>
{
public T Value { get; set; }
//...
}
public class DataStructure<T>
{
public EnvironmentData Environment { get; init; } = new();
public class EnvironmentData : Category
{
public LightData Light { get; init; } = new();
public List<Force> Forces { get; init; } = new();
public class LightData : Category
{
public LightPositionData LightPosition { get; init; } = new();
public DataProperty<T> Intensity { get; init; } = new();
public class LightPositionData : Category
{
public DataProperty<List<T>> OriginCoordsXYZ { get; init; } = new();
public DataProperty<List<T>> TargetCoordsXYZ { get; init; } = new();
}
}
public class Force : Category
{
public DataProperty<T> Magnitude { get; init; } = new();
}
}
//...
}
While the ConvertAndCopyData
function could be implemented by explicitly addressing each category, sub-category and data property, it would require me to essentially type out the whole structure once again, and maintain this function manually if I change anything in the original DataStructure
. The DataStructure
in my actual application is far more extensive, rendering this approach impractical.
I am hoping that this can be achieved through reflection, as performance is not critical here. But I have no idea how to do it. Can anyone advise?
You can use AutoMapper
to convert an object to another object. You have to create a mapping for the classes with the generic types, see AutoMapper - Features - Open Generics. The mapping could look like this:
var config = new MapperConfiguration(cfg => {
IList<Type> types = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(it => it.IsAssignableTo(typeof(ICategory)))
.ToList();
foreach (Type type in types)
{
cfg.CreateMap(type, type);
}
cfg.CreateMap(typeof(DataStructure<>), typeof(DataStructure<>));
cfg.CreateMap(typeof(DataProperty<>), typeof(DataProperty<>));
});
You can use reflection to get all the classes, that implements the ICategory
interface (see Getting all types that implement an interface) and add the types via CreateMap()
to the mapper configuration. Since DataStructure<T>
and DataProperty<T>
does not have such a base class or other kind of indication, you have to explicit add the mappings for these classes.
When you create your mapper with config.CreateMapper();
you can map your objects with:
mapper.Map(dataStructureStr, dataStructureDbl);