C # generics class factory question

I want to control the creation of a group of classes, all of which have a common interface, and all of them need some logic in the construction. In addition, I do not want any code other than the factory class to be able to create objects from these classes.

My main stumbling blocks:

(1) for a generic method, in order to be able to instantiate classes I need a new () constraint, which means I have to have an open class constructor, which means they can be created publicly.

(2) An alternative would be for the classes themselves to have a static method that returns an instance of the class. But I cannot call it from my general class, because I need to deal with terms / types, and you cannot have static through interfaces.

Here is what I got at the moment, but it uses the new () constraint, which allows me to publish my classes publicly:

internal static class MyClassFactory { internal static T Create<T>(string args) where T : IMyType, new() { IMyType newThing = new T(); newThing.Initialise(string args); return (T)newThing; } } public interface IMyType { void Initialise(string args); } public class ThingA: IMyType { public void Initialise(string args) { // do something with args } } 

Any help is much appreciated :)

+7
source share
6 answers

Consider the reflection approach.

Just create class constructors as private, so you cannot create them in the usual way, and your factory will call this private class constructor using reflection.

Reflection affects performance, but calling this constructor is not a big reflection operation.

Check out this MSDN article to learn more about how to invoke a private constructor using reflection:

But this can be summarized using this piece of code:

 typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Private, Type.EmptyTypes, Type.DefaultBinder, null).Invoke(null); 

UPDATE

By the way, I believe that this can affect the performance and complexity of the code, because you do not want developers to instantiate the class, so using this factory method is not a good reason.

Sometimes good developer guilds are better than any code restriction. I say this because in your case I would implement this factory method with a common parameter T and the same general restriction, and I, if in the documentation documents say "if you want an instance of type T you need to use factory", and some decide not to follow this rule, it was not my responsibility, and with this problem it would be answered: "The manual says that you need to use the factory."

Good habits are better than security code to solve human decisions.

+1
source

It looks like you are trying to start your own service locator.

Have you considered the Dependency Injection (DI) approach? as there are reasons why you might need to avoid a service locator.

I highly recommend you take a look at some of the popular IOC containers that can perform the functions that you are trying to build. Looking back, I am very glad that I chose DI through the service locator.

- Ninject

- Autofac

- Unity

+3
source

There is something you can do, but it is really ugly ...

 public class ThingA: IMyType { [Obsolete("This constructor must not be called directly", true)] public ThingA() { } public void Initialise(string args) { // do something with args } } 

This will lead to a compilation error if you try to call the constructor explicitly, but you will not prevent it from being called in a generic method with the new() constraint.

+2
source

What you can do is make a (hidden) convention that all objects that implement your interface also have a specific constructor that the factory can call. This is how ISerializable works. The disadvantage is that the existence of a constructor is not checked by the compiler. In the factory, find the constructor through reflection and call the correct arguments. The constructor can be protected.

 // Get constr with string arg var constr = theType.GetConstructor(new[]{typeof(String)}); T result = (T)constr.Invoke(new[]{"argString"}); 
+1
source

This might work for you - using reflection instead of the new () constraint. Also note that the constructor is private, and you need to add a method to the derived class that returns an instance of itself (static):

 internal static class MyClassFactory { internal static T Create<T>(string args) where T : IMyType { IMyType newThing = (T)typeof(T).GetMethod("GetInstance").Invoke(default(object), null); newThing.Initialise(args); return (T)newThing; } } public interface IMyType { void Initialise(string args); } public class ThingA: IMyType { private ThingA() { } public static IMyType GetInstance() { return new ThingA(); // control creation logic here } public void Initialise(string args) { // do something with args } } 

EDIT

Just to clarify this, as indicated, you can access the private constructor through reflection, for example:

 internal class MyClassFactory { internal static T Create<T>(string args) where T : IMyType { IMyType newThing = (T)typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.DefaultBinder, Type.EmptyTypes, null).Invoke(null); newThing.Initialise(args); return (T)newThing; } } public interface IMyType { void Initialise(string args); } public class ThingA : IMyType { private ThingA() { } public void Initialise(string args) { Console.WriteLine(args); } } 
+1
source

Define the IFactory <out T> interface with the Create method, and then for each class that you want to create, define a static object that implements IFactory <TT>. Then you can either go around the IFactory <TT> object wherever you want to create a TT, or you can create a static FactoryHolder <T> class with which you then register the relevant plants. Note that generic classes have a separate set of static variables for each combination of generic types, and this can provide a very convenient and safe type of storing static mapping between types and factory instances.

One small caveat is that you must register a factory for every exact type you want to find. You can register the derived factory type as the manufacturer of the base type (so that, for example, FactoryHolder <Car> .GetFactory () returns IFactory <FordFocus2Door>), but if it does not explicitly register the factory with the specified exact type T, FactoryHolder <T> will be empty. For example, either IFactory <FordFocus2Door> or IFactory <FordFocus4Door> can be used as IFactory <FordFocus>, but if one of them is not registered as IFactory <FordFocus>, then they will not be returned FactoryHolder <FordFocus> .GetFactory ().

0
source

All Articles