Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dilemma in calling constructor of generic class

I have this generic singleton that looks like this:

public class Cache<T>
{
    private Dictionary<Guid, T> cachedBlocks;

    // Constructors and stuff, to mention this is a singleton

    public T GetCache(Guid id)
    {
        if (!cachedBlocks.ContainsKey(id))
            cachedBlocks.Add(id, LoadFromSharePoint(id))
        return cachedBlocks[id];
    }

    public T LoadFromSharePoint(Guid id)
    {
        return new T(id)    // Here is the problem.
    }
}

The error message is:

Cannot create an instance of type T because it does not have the new() constraint.

I have to mention that I must pass that id parameter, and there is no other way to do so. Any ideas on how to solve this would be highly appreciated.

like image 365
Alex Oltean Avatar asked Feb 21 '13 13:02

Alex Oltean


People also ask

Can we call constructor multiple times?

Constructors are called only once at the time of the creation of the object.

How do you call a constructor of a generic class in C#?

You must be sure the type defines the constructor you want to invoke. You cannot constrain the generic type T to types that have a particular constructor other than the default constructor. (E.g. where T : new(Guid) does not work.)

Do generic classes have constructors?

Constructors are similar to methods and just like generic methods we can also have generic constructors in Java though the class is non-generic. Since the method does not have return type for generic constructors the type parameter should be placed after the public keyword and before its (class) name.

How do you construct a generic constructor?

Generic constructors are the same as generic methods. For generic constructors after the public keyword and before the class name the type parameter must be placed. Constructors can be invoked with any type of a parameter after defining a generic constructor.


2 Answers

Normally you would constrain the type T to a type that has a default constructor and call that. Then you'd have to add a method or property to be able to provide the value of id to the instance.

public static T LoadFromSharePoint<T>(Guid id)
    where T : new()     // <-- Constrain to types with a default constructor
{
    T value = new T();
    value.ID = id;
    return value;
}

Alternatively since you specify that you have to provide the id parameter through the constructor, you can invoke a parameterized constructor using reflection. You must be sure the type defines the constructor you want to invoke. You cannot constrain the generic type T to types that have a particular constructor other than the default constructor. (E.g. where T : new(Guid) does not work.)

For example, I know there is a constructor new List<string>(int capacity) on List<T>, which can be invoked like this:

var type = typeof(List<String>);
object list = Activator.CreateInstance(type, /* capacity */ 20);

Of course, you might want to do some casting (to T) afterwards.

like image 145
Daniel A.A. Pelsmaeker Avatar answered Nov 23 '22 14:11

Daniel A.A. Pelsmaeker


To do this you should specify what T is. Your Cache<T> can hold anything? Tiger, Fridge and int as well? That is not a sound design. You should constrain it. You need an instance of T which will take a Guid to construct the instance. That's not a generic T. Its a very specific T. Change your code to:

public class Cache<T> where T : Cacheable, new()
{
    private Dictionary<Guid, T> cachedBlocks;

    // Constructors and stuff, to mention this is a singleton

    public T GetCache(Guid id)
    {
        if (!cachedBlocks.ContainsKey(id))
            cachedBlocks.Add(id, LoadFromSharePoint(id))
        return cachedBlocks[id];

       //you're first checking for presence, and then adding to it
       //which does the same checking again, and then returns the
       //value of key again which will have to see for it again. 
       //Instead if its ok you can directly return

       //return cachedBlocks[id] = LoadFromSharePoint(id);

       //if your LoadFromSharePoint is not that expensive.
       //mind you this is little different from your original 
       //approach as to what it does.
    }

    public T LoadFromSharePoint(Guid id)
    {
        return new T { Key = id };    // Here is no more problem.
    }
}

public interface Cacheable
{
    Guid Key { get; set; }
}

Now derive all the cacheables (whatever Ts that you will pass it for Cache<T>) from the interface Cacheable.

like image 42
nawfal Avatar answered Nov 23 '22 15:11

nawfal