I am trying to create a generic class which new\'s up an instance of the generic type. As follows:
public class HomepageCarousel : List
I'd Probably go for the suggestion from Tony "jon" the Skeet pony but there's another way of doing it. So mostly for the fun here's a different solution (that have the down side of failing at runtime if you forget to implement the needed method but the upside of not having to supply a factory method, the compiler will magically hook you up.
public class HomepageCarousel<T> : List<T> where T: IHomepageCarouselItem
{
private List<T> GetInitialCarouselData()
{
List<T> carouselItems = new List<T>();
if (jewellerHomepages != null)
{
foreach (PageData pageData in jewellerHomepages)
{
T homepageMgmtCarouselItem = null;
homepageMgmtCarouselItem = homepageMgmtCarouselItem.create(pageData);
carouselItems.Add(homepageMgmtCarouselItem);
}
}
return carouselItems;
}
}
public static class Factory
{
someT create(this someT, PageData pageData)
{
//implement one for each needed type
}
object create(this IHomepageCarouselItem obj, PageData pageData)
{
//needed to silence the compiler
throw new NotImplementedException();
}
}
just to repeat my "disclaimer" this is very much ment to serve as a reminder that there can be rather different approaches to solving the same problem they all have draw backs and strengths. One draw back of this approach is that it's part black magic ;)
T homepageMgmtCarouselItem = null;
homepageMgmtCarouselItem = homepageMgmtCarouselItem.create(pageData);
but you avoid the perculiar constructor taking a delegate argument. (but I usually go for that approach unless I was using a dependency injection mechanism to supply the factory class for me. Which insidentally is the kind of DI framework I'm working on in my sparetime ;p)
Jared's answer is still a good way to go - you just need to make the constructor take the Func<PageData, T>
and stash it for later:
public class HomepageCarousel<T> : List<T> where T: IHomepageCarouselItem
{
private readonly Func<PageData, T> factory;
public HomepageCarousel(Func<PageData, T> factory)
{
this.factory = factory;
}
private List<T> GetInitialCarouselData()
{
List<T> carouselItems = new List<T>();
if (jewellerHomepages != null)
{
foreach (PageData pageData in jewellerHomepages)
{
T homepageMgmtCarouselItem = factory(pageData);
carouselItems.Add(homepageMgmtCarouselItem);
}
}
return carouselItems;
}
Then you just pass the function into the constructor where you create the new instance of the HomepageCarousel<T>
.
(I'd recommend composition instead of inheritance, btw... deriving from List<T>
is almost always the wrong way to go.)
Have you considered using Activator (this is just another option).
T homepageMgmtCarouselItem = Activator.CreateInstance(typeof(T), pageData) as T;
It's a C# and CLR handicap, you cannot pass an argument to new T(), simple.
If you're coming from a C++ background this used be NOT-broken and TRIVIAL. PLUS you don't even require an interface/constraint. Breakage all over the place, and without that functional factory 3.0 hack you are forced to do 2-pass initialisation. Managed Blasphemy!
Do the new T() first and then set the property or pass an exotic initialiser syntax or as all well suggested use the Pony's functional workaround.. All yucky but that's the compiler and runtime idea of 'generics' for you.
Why don't you just put a static "constructor" method on the interface? A little hacky I know, but you gotta do what you gotta do...
Just to add to other answers:
What you are doing here is basically called projection. You have a List
of one type and want to project each item (using a delegate) to a different item type.
So, a general sequence of operations is actually (using LINQ):
// get the initial list
List<PageData> pageDataList = GetJewellerHomepages();
// project each item using a delegate
List<IHomepageCarouselItem> carouselList =
pageDataList.Select(t => new ConcreteCarousel(t));
Or, if you are using .Net 2.0, you might write a helper class like:
public class Project
{
public static IEnumerable<Tdest> From<Tsource, Tdest>
(IEnumerable<Tsource> source, Func<Tsource, Tdest> projection)
{
foreach (Tsource item in source)
yield return projection(item);
}
}
and then use it like:
// get the initial list
List<PageData> pageDataList = GetJewellerHomepages();
// project each item using a delegate
List<IHomepageCarouselItem> carouselList =
Project.From(pageDataList,
delegate (PageData t) { return new ConcreteCarousel(t); });
I'm not sure how the rest of the code looks like, but I believe that GetInitialCarouselData
is not the right place to handle the initialization, especially since it's basically duplicating the projection functionality (which is pretty generic and can be extracted in a separate class, like Project
).
[Edit] Think about the following:
I believe right now your class has a constructor like this:
public class HomepageCarousel<T> : List<T>
where T: IHomepageCarouselItem, new()
{
private readonly List<PageData> jewellerHomepages;
public class HomepageCarousel(List<PageData> jewellerHomepages)
{
this.jewellerHomepages = jewellerHomepages;
this.AddRange(GetInitialCarouselData());
}
// ...
}
I presume this is the case, because you are accessing a jewellerHomepages
field in your method (so I guess you are storing it in ctor).
There are several problems with this approach.
You have a reference to jewellerHomepages
which is unneccesary. Your list is a list of IHomepageCarouselItems, so users can simply call the Clear() method and fill it with anything they like. Then you end up with a reference to something you are not using.
You could fix that by simply removing the field:
public class HomepageCarousel(List<PageData> jewellerHomepages)
{
// do not save the reference to jewellerHomepages
this.AddRange(GetInitialCarouselData(jewellerHomepages));
}
But what happens if you realize that you might want to initialize it using some other class, different from PageData
? Right now, you are creating the list like this:
HomepageCarousel<ConcreteCarousel> list =
new HomepageCarousel<ConcreteCarousel>(listOfPageData);
Are you leaving yourself any option to instantiate it with anything else one day? Even if you add a new constuctor, your GetInitialCarouselData
method is still too specific to use only PageData
as a source.
Conclusion is: Do not use a specific type in your constructor if there is no need for it. Create actual list items (concrete instances) somewhere else.