I have following situation. I have an interface for generating data, this data can be in different formats (int, double, Dictionary and etc.). I want to be able to iterate over all classes and hit "GenerateData" method of every generator (GPS, Fuel and etc). The code that I already try look as this
private List<IDataGenerator> _dataGenerators = new List<IDataGenerator>()
{
new GPSDataGenerator(), //return Dictionary<int, double>
new FuelDataGenerator() //return int
};
public IEnumerable<T> GenerateData<T>(int cycleId)
{
foreach (var generator in _dataGenerators)
{
//Go over all generators and call generator.GenerateData().
}
}
After this all generated data should have ID, so later I can search for it. For example for ID = 1 I should have one record of all data generators.
For example I have new class Car and CarGeneratorRepository. When Car is created in _carGeneratorRepository.Add(will add all generators 1 by 1). This CarGeneratorRepository will be responsible to add generators, save data and search for data.
Do you think repository pattern is good choice? If after a week I have to add new generator TirePresureDataGenerator that return new Dictionary<int, MyValue>()
This is probably not a good pattern to use. Generics is a compile time pattern, in runtime a MyClass<double>
is completely different from MyClass<int>
. There are some exceptions like variance and reflection, but that is probably not relevant in this case.
You should usually try to avoid mixing classes of different interfaces in the same list. If you want runtime variant types you can always use object. That should make your GenerateData
trivial to implement.
public interface IDataGenerator{
object GenerateData();
}
You obviously lose type safety with this approach, and will need to use runtime type checks to inspect the actual data type if you want to use the generated objects for anything. Luckily, these kind of type checks are fairly easy to do in modern C#.
A somewhat better approach might be to use a common interface for all generated data:
public interface IDataGenerator{
IData GenerateData();
}
public interface IData{
int Id {get;}
}
But you will still need type checks if you want to do anything more than getting the Id.
If you want type safety you could use the visitor pattern:
public interface IData{
int Id {get;}
T Accept<T>(IDataVisitor<T> visitor);
}
public class IntData : IData{
T Visit(IDataVisitor<T> visitor) => visitor.Visit(this);
}
public interface IDataVisitor<T> {
T Visit(IntData data);
}
This is a kind of 'double dispatch', i.e. the called method depend both on the type of the data, and the type of the visitor. An possible advantage of this is that you can use the compiler to force you to update all visitors, so when you add a new data-type there is no risk of forgeting to update all the switch statements spread all over the code that checks the type.