I want to store multiple objects in the database. The type of objects are not fixed and can be dynamically added by loaded modules. At one part in my application I want to search for the correct implementation to invoke the method with the created object.
One example. I have 3 objects:
public class Employee : Person { }
public class Supervisor : Person { }
public abstract class Person { }
and there are implementations of IExporter registered at the DI container:
public interface IExporter<T> where T: Person
{
Task ExportAsync(T person);
}
public class EmployeeExporter : IExporter<Employee>
{
public Task ExportAsync(Employee exployee) => Task.CompletedTask; // TODO
}
public class SupervisorExporter : IExporter<Supervisor>
{
public Task ExportAsync(Supervisor supervisor) => Task.CompletedTask; // TODO
}
How would my person factory which returns a person would know which Exporter is the right one to choose for export?
var type = typeof(IExporter<>).MakeGenericType(person.GetType());
var exporter = (IExporter<Employee>)serviceProvider.GetRequiredService(type);
await exporter.ExportAsync(person);
Something like this but without explicitly specify the IExporter<Employee>
cast.
Or do I something completely wrong?
I already mentioned Jimmy Bogard's article, so I won't repeat that. Besides the options mentioned by Jimmy, there's another option, which is to use C# dynamic. This looks like this:
var type = typeof(IExporter<>).MakeGenericType(person.GetType());
dynamic exporter = serviceProvider.GetRequiredService(type);
await exporter.ExportAsync((dynamic)person);
At runtime, the C# compiler goes look for a method called ExportAsync
. This is about the most concise solution you can get. But be aware of the following downsides:
IExporter<T>
interface, this code keeps compiling but fails at runtime. You should add a unit test for your factory to make sure it still works.dynamic
keyword, you can only invoke public
methods on public
classes. Even if your IExporter<T>
is defined as public
, when the exporter implementation (or the outer-most decorator your decided to wrap it with) is internal
, the invocation will fail. To me this seems like a quirk in the C# compiler, because IMO it should be able to call ExportAsync
when its interface is public, but that's not how it works. So again, you might want to add some unit tests to make sure it works.What it comes down to when using dynamic
, is that you'll be adding more tests than with the approaches Jimmy suggests. His solutions have more code and need less testing. Dynamic needs no extra code, but more testing code.