简体   繁体   中英

How can I create class instance from type parameter?

I would like to create a little class factory what uses a dictionary, but I'm stuck on a point where I have to divide the creation of an instance and get an instance from the dictionary.

Instantiate looks like this:

public void CreateInstance<T>(string id) where T : class, new()
{
    lock (_syncroot)
    {
        _internal_dict.Add(id, new T());
    }
}

It's working well, but the problem is, that what can I do if I would like to instantiate a class what has to be parameterized constructor?

Is there any solution for it, or an architectural best practice?

With no contraints on T other than being a class your only real option is to use Refelction via the Activator class.

//Still used for the no parameter constructor checking.
public void CreateInstance<T>(string id) where T : class, new()
{
    lock (_syncroot)
    {
        _internal_dict.Add(id, new T());
    }
}

public void CreateInstance<T>(string id, params object[] args) where T : class
{
    lock (_syncroot)
    {
        _internal_dict.Add(id, Activator.CreateInstance(typeof(T), args));
    }
}

Note that this removes the ability to do compile time checking that the class you are choosing for <T> has a proper public constructor that can be called and may throw a exception at run-time if no constructor can be found.

It is not possible to constrain a type parameter so that it requires a class to have a parameterized constructor.

The best you can do is to require the class to implement an interface which has a factory method, then call the factory method from inside the generic method.

From "Essential C#", 4th edition:

public class EntityBase<TKey>
{
    public EntityBase(TKey key)
    {
        Key = key;
    }

    public TKey Key
    {
        get {return _key;}
        set {_key = value;}
    }
    private TKey _key;
}

public class EntityDictionary<TKey, TValue, TFactory> :
    Dictionary<TKey, TValue>
    where TKey : IComparable<T>, IFormattable
    where TValue : EntityBase<TKey>
    where TFactory : IEntityFactory<TKey, TValue>, new()
{
    public TValue New(TKey key)
    {
        TValue newEntity = new TFactory().CreateNew(key);
        Add(newEntity.Key, newEntity);
        return newEntity;
    }
}

public interface IEntityFactory<TKey, TValue>
{
    TValue CreateNew(TKey key);
}

The EntityDictionary class needs to create an instance of EntityBase , but that requires a parameterized constructor. To get around that, create a class that implements IEntityFactory<TKey, TValue> , and which has a default constructor. That class will know how to call the EntityBase constructor.

It seems to me that you are trying to create an object repository, not a factory. For a repository it is easier to instantiate the objects before adding them to repository.

repository.Add(key, new SomeType(arg1, arg2));

If all the objects to be added to the repository derive from a common type or implement some common interface then simply use a typed Dictionary<TKey, TValue> . Example:

private Dictionary<string, IGameObject> _gameObjects = 
                                                new Dictionary<string, IGameObject>();

_gameObjects.Add("car", new Car(maxSpeed));
_gameObjects.Add("monster", new Monster());

And retrieve it with:

IGameObject obj = _gameObjects(key);

If you intend to store completely unrelated types then use a compound key consisting of the type and possibly an identifier.

public class Repository
{
    private class RepositoryKey
    {
        public RepositoryKey(Type type, string key)
        {
            this.Type = type;
            this.Key = key;
        }

        public Type Type { get; private set; }
        public string Key { get; private set; }

        //TODO: override Equals and GetHashCode.
    }

    private Dictionary<RepositoryKey, object> _dict =
                                              new Dictionary<RepositoryKey, object>();

    public void Add<T>(string key, T obj)
    {
        _dict.Add(new RepositoryKey(typeof(T), key), obj);
    }

    public Get<T>(string key)
    {
        return (T)_dict[new RepositoryKey(typeof(T), key)];
    }
}

This is just a minimalistic implementation with no error handling.

var rep = new Repository();
rep.Add("slow", new Car(30));
rep.Add("fast", new Car(180));
rep.Add("left", new Monster(x1, y1));
rep.Add("right", new Monster(x2, y2));

...

Car fastCar = rep.Get<Car>("fast");
Monster leftMonster = rep.Get<Monster>("left");

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM