Reputation: 143
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
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
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 T
s that you will pass it for Cache<T>
) from the interface Cacheable
.
Upvotes: 7
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