Subject
This is simplified example of implementing a factory pattern in a way to restrict direct access to constructors and prevent illegal ways to create objects from other assemblies.
In other words I want to limit other developers to only be able to create their objects using this factory. (there are things must be done during object creation which is not specific to one object but related to all objects hence the reason)
Solution
I have this delegate which is used to create object instances inside my factory.
internal delegate T Instantiate<out T>(/*parameters*/) where T : Vehicle;
As you can see this delegate is internal, all constructors related to Vehicle
and its sub-types
are also internal.
internal interface IVehicle{/*...*/}
public class Vehicle
{
//constructor is not protected. so this cant be intertied from outside assembly.
internal Vehicle(/*parameters*/){ }
}
public class Car : Vehicle
{
internal Car(/*parameters*/) : base(/*parameters*/) { }
}
Now here is the trick.
/// <summary>
/// Choose from one of predefined containers to create your vehicle from factoy.
/// </summary>
/// <typeparam name="T">type of vehicle</typeparam>
public class InstanceContainer<T> where T : Vehicle
{
internal InstanceContainer(Instantiate<T> instance) // internal
{
GetInstance = instance;
}
// delegate which creates new instance of T
internal Instantiate<T> GetInstance { get; }
}
This makes others be able to choose their delegate but not access actual delegate. here are some options they can choose.
/// <summary>
/// Choose this to create car from factory.
/// </summary>
public static InstanceContainer<Car> CarInstance { get; } = new InstanceContainer<Car>(CreateCar);
private static readonly Instantiate<Car> CreateCar = (/*parameters*/) => new Car(/*parameters*/);
// ... others options like Plane, Ship etc
Example
Here is one of the methods to create an object from factory.
public static T CreateVehicle<T>(/*parameters,*/ InstanceContainer<T> instance) where T : Vehicle
{
T obj;
//...
// At some point:
obj = instance.GetInstance(/*parameters*/);
//...
return obj;
}
Now here is how to use it. (from other assemblies say create a Car
)
VehicleFactory.CreateVehicle(/*parameters,*/ VehicleFactory.CarInstance);
This works fine, it complies fine and runs fine till now.
Note: VehicleFactory is public static class holding instances, methods, delegates...
Question
Now I want to make InstanceContainer
co-variant so I can pass instances abstractly.
// turns long name into short one!
using VehicleInstance = Factories.VehicleFactory.InstanceContainer<Factories.Goods.Vehicle>
//...
//...
VehicleInstance instance;
// instance will be choosen based on some conditions. like this:
if(true)
instance = VehicleFactory.CarInstance; // we got magic co-variant here!
else
instance = VehicleFactory.PlaneInstance;
VehicleFactory.CreateVehicle(/*parameters,*/ instance);
Is there any way to make InstanceContainer
co-variant but not expose its delegate to other assemblies?
This is what I have tried so far:
public interface IContainer<out T>
{
// empty!
}
internal class InstanceContainer<T> : IContainer<T> where T : Vehicle
{
internal InstanceContainer(Instance<T> instance)
{
GetInstance = instance;
}
internal Instance<T> GetInstance { get; }
}
This time IContainer
is public interface and InstanceContainer
is internal. that means we must use IContainer
instead for safe access. little things must change:
// works fine!
public static IContainer<Car> CarInstance { get; } = new InstanceContainer<Car>(CreateCar);
public static T CreateVehicle<T>(/*parameters,*/ IContainer<T> instance)
{
T obj;
//...
// At some point:
obj = ((InstanceContainer<T>)instance).GetInstance(/*parameters*/); // runtime error.
//...
return obj;
}
Problem and Conclusion
The problem appears at runtime. we can not cast co-variant interface back to its actual class implementation.
Sorry for long question. but I know without subject, the answer for title would be pretty straight forward:
"You cant. because generic variants are pinned to interfaces. interfaces are pinned to public implements. it cant be internal. that's c# limit thing :("
I've also tried explicit implements but that does not work either. you basically end up with public interface and that will expose internal delegate anyway.
So is there better way to implement such factory pattern or am I using wrong design pattern? maybe I'm overthinking and solution is simple.
Thanks for giving your time and answer.