Reputation: 718
I want to do something at runtime with objects based on there type. Depending on the type I need to do different operations so I thought I would create an abstract class like this:
internal abstract class DataOperator<T>
where T : class
{
public abstract void DoSomething(IList<T> data);
}
with multiple concrete implementations like:
class MyClassOperator : DataOperator<MyClass>
{
public override void DoSomething(IList<MyClass> data)
{
throw new NotImplementedException();
}
}
But how would I actually create an instance of the concrete class at runtime?
Upvotes: 0
Views: 211
Reputation: 718
I resolved this issue using a factory as suggested by Matthew Watson.
DataOperator
is actually AggregateRootMigrator
public static class AggregateRootMigratorFactoryProvider
{
public static AggregateRootMigrator<T> GetAggregateRootMigrator<T>()
where T : AggregateRoot
{
var type = typeof(AggregateRootMigrator<T>).Assembly.GetTypes().Where(
t =>
{
if (t.IsAbstract == false && t.IsSubclassOf(typeof(AggregateRootMigrator<T>)))
{
return true;
}
return false;
}).SingleOrDefault();
if (type == null)
{
throw new InvalidOperationException($"No Factory found for eventType: {typeof(T).Name}");
}
return (AggregateRootMigrator<T>)Activator.CreateInstance(type);
}
}
now I can use it like this: var dataMigrator = AggregateRootMigratorFactoryProvider.GetAggregateRootMigrator(); var result = dataMigrator.Migrate(data);
Upvotes: 0
Reputation: 112259
The problem is that DataOperator<A>
and DataOperator<B>
are not assignment compatible. Not even if A
and B
are. So, you cannot assign your classes to, say DataOperator<object>
. Therefore, you will have to treat them individually as non related types.
A way out is to have a non generic base type or interface.
interface IDataOperator
{
public void DoSomething(IList data);
}
We create an abstract class that implements it explicitly in order to hide this weakly typed DoSomething
when not called through the interface. We also introduce the generic type parameter.
abstract class DataOperator<T> : IDataOperator
where T : class
{
void IDataOperator.DoSomething(IList data)
{
if (data is IList<T> listT) {
DoSomething(listT);
} else {
throw new ArgumentException("The List is not compatible.", nameof(data));
}
}
abstract public void DoSomething(IList<T> data);
}
We implement concrete types like this:
class DataOperatorA : DataOperator<MyClassA>
{
public override void DoSomething(IList<MyClassA> data)
{
throw new NotImplementedException();
}
}
class DataOperatorB : DataOperator<MyClassB>
{
public override void DoSomething(IList<MyClassB> data)
{
throw new NotImplementedException();
}
}
These implementations are assignment compatible to our interface. Example:
IDataOperator[] operators = { new DataOperatorA(), new DataOperatorB() };
If you want to create different operators depending on other data, you can create a factory class. In this example, we use a string as discriminator, but it could be anything else, like a System.Type
or an enum, etc.
static class DataOperator
{
public static IDataOperator Create(string op)
{
return op switch {
"A" => new DataOperatorA(),
"B" => new DataOperatorB(),
_ => throw new ArgumentException("Unknown operator.", "op")
};
}
}
Same example as above but with the factory:
IDataOperator[] operators = { DataOperator.Create("A"), DataOperator.Create("B") };
Upvotes: 2