Alex Oltean
Alex Oltean

Reputation: 143

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.

Upvotes: 14

Views: 19103

Answers (3)

User
User

Reputation: 11

In order to use the constructor of a Generic Type without any constraint, and within the class, the syntax where T : class, new() needs to be used

This enables to change values of attributes (fields) - not only get/set properties) at runtime depending the target class used

First, declaring the generic class:

public class Foo<T>   where T : class, new()
{
    public T oneEmptyElement()
    {
        return new T();
    }

    public T setAttribute(string attributeName, string attributeValue)
    {
        T objT = new T();
        System.Reflection.FieldInfo fld = typeof(T).GetField(attributeName);
        if (fld != null)
        {
            fld.SetValue(objT, attributeValue);
        }
        return objT;
    }

    public List<T> listOfTwoEmptyElements()
    {
        List<T> aList = new List<T>();
        aList.Add(new T());
        aList.Add(new T());
        return aList;
    }
}

Declare then a potential target class:

public class Book
{
    public int name;
}

And finally the call can be done like this:

        Foo<Book> fooObj = new Foo<Book>();

        Book aBook = fooObj.oneEmptyElement();
        aBook.name = "Emma";

        Book anotherBook = fooObj.setAttribute("name", "John");

        List<Book> aListOfBooks = fooObj.listOfTwoEmptyElements();
        aListOfBooks[0].name = "Mike";
        aListOfBooks[1].name = "Angelina";

        Console.WriteLine(aBook.name);    //Output Emma
        Console.WriteLine(anotherBook.name);    //Output John
        Console.WriteLine(aListOfBooks[0].name); // Output Mike
        Console.WriteLine(aListOfBooks[1].name);  // Output Angelina

Upvotes: 0

nawfal
nawfal

Reputation: 73311

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.

Upvotes: 7

Daniel A.A. Pelsmaeker
Daniel A.A. Pelsmaeker

Reputation: 50376

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.

Upvotes: 25

Related Questions