Click to See Complete Forum and Search --> : Creating genric class that only accepts certain types


DeepT
August 24th, 2007, 04:30 PM
I would like to create generic class that can only take a limited kind of classes as an argument.

IE: I have a bunch of Job classes (decended from Job) and I want to create a Job Manager that will take a list of data and a class type, and then create a bunch of instances of "CLASS" initalized with the data.

So it might looked like:
class JobManager<T>
{
private List<JOB> MyJobs = new List<JOB>;

public JobManger(string Data)
{
// Some loop that makes a bunch of these
MyJobs.Add(new T(Data));
}


That is fine, except for the fact that if "T" isn't a kind of Job, this will choke. IE: new JobManager<int>;

Rudegar
August 24th, 2007, 05:27 PM
maybe you can use

class JobManager<T>
where T : "one of your wanted input"

Zaccheus
August 24th, 2007, 06:31 PM
Yes, you can use the 'where' keyword to specify a base class/interface which T must be derived from:

http://msdn2.microsoft.com/en-us/library/ms379564(VS.80).aspx#csharp_generics_topic4

Mutant_Fruit
August 24th, 2007, 08:59 PM
MyJobs.Add(new T(Data));

You can't do that either ;) You can only specify that there is a parameterless constructor, so you'd have to have:


where T : new()


You could then instantiate as:

T t = new T();
MyJobs.Add(t);

You should be able to cast 'T' to type 'Job' can set your data to it, but i dunno. There's limitations on casting 'T' in generic methods which i cant remember the specifics of.

trenches
August 26th, 2007, 03:37 AM
If you have all the job construction logic encapsulated within the JobManager class then simply using

class JobManager<T> where T : JOB

should be adequate, however, you should consider (if you haven't already) distributing that logic using a creational design pattern (e.g., Builder, etc.) and passing the specific subtype(s) to the JobManager's constructor

public JobManager(JOB[] jobs)

DeepT
August 27th, 2007, 09:28 AM
Thanks, that is what I needed. The JobManager is the thing that will be doing all the construction of Jobs because it will be the only class to know how many Jobs to construct.

DeepT
August 27th, 2007, 10:51 AM
MyJobs.Add(new T(Data));

You can't do that either ;) You can only specify that there is a parameterless constructor, so you'd have to have:


where T : new()


You could then instantiate as:

T t = new T();
MyJobs.Add(t);

You should be able to cast 'T' to type 'Job' can set your data to it, but i dunno. There's limitations on casting 'T' in generic methods which i cant remember the specifics of.


Wait... so I can't use constuctors that use parameters with generic classes? That will be a problem.

trenches
August 27th, 2007, 12:18 PM
You shouldn't have trouble with constructor parameters in a generic class. Try this code out. . .

public class Program
{
static void Main(string[] args)
{
JobManager<JobA> j1 = new JobManager<JobA>(new string[] { "A", "B" });
JobManager<JobB> j2 = new JobManager<JobB>(new string[] { "C", "D" });
}
}
public class JobManager<T> where T : Job
{
public JobManager(string[] data) { }
}
public class Job { public Job(string data) { } }
public class JobA : Job { public JobA(string data) : base(data) { } }
public class JobB : Job { public JobB(int data) : base(data.ToString()) { } }

You may choose to have a parameterless constructor on the base class, Job, but as long as you can construct an instance of Job from all of the subclasses, the parameters shouldn't matter. . .hope that helps.

DeepT
August 27th, 2007, 02:11 PM
I reworked my classes so they have generic constructors. I do not like doing that because if I have certain methods the require certain initial data to work, I can't be sure the user provided the information yet, IE: Did they call MyClass.Init(data);

Sure I can put in a bunch of "if (wasInit == true )" stuff, but that is messy. With the constructor approach, I know the class can't be constructed without the required data and therefore the methods can not be called unless they have their data.

The approach you provided requires that the JobManager to know about all the classes you make. IE: I make a new Job_Something, now JobManager needs to have it added. It is also messy. I decided to forgo the constructors that take parameters, and require an Init method. I do not like it, but it is the cleanest method I can think of. At least if I add new Job classes, I do not need to edit any other code to support them.

Zaccheus
August 27th, 2007, 02:16 PM
Can you not also specify an interface which has a factory method?

DeepT
August 27th, 2007, 03:08 PM
Don't you think that would be a bit overkill? What I really need is the ability to pass a TYPE as a parameter. The generic class T is as close as you can get as far as I know. Also a factory would be yet another chunk of code to maintain in parallel with any new Job subclasses, it is pretty much what trenches suggested in a manner of speaking.

Zaccheus
August 27th, 2007, 05:10 PM
It would mean that JobManager does not need to know about all the classes you make and you don't need the Init method either.

Zaccheus
August 27th, 2007, 05:29 PM
I was thinking of something like this:


using System.Collections.Generic;

interface IJob
{
void DoSomething();
}

interface Builder<T> where T : IJob
{
T Build(string data);
}

class JobManager<T> where T : IJob
{
private List<T> myJobs = new List<T>();

public JobManager(Builder<T> builder, string data)
{
myJobs.Add(builder.Build(data));
}
}


I agree it might be overkill, but it's an option.

DeepT
August 27th, 2007, 05:37 PM
Ok. Well the saga here continues.

Suppose I now have my Generic class, JobManager. Now suppose I want to send it back in an event, using the "this" pointer, ie: SomeEvent(this, null).

I have some properties of JobManager that would be useful to the event handler (such as how many jobs there are total, how many have completed, etc...). The problem is that in my event handler, I can't use the "sender" because I can't cast it as some kind of JobManager. I need to give it a type, like Job_Validator or so.

The event handler will not know what specifically "sender" is. That is ok, because I do not wish to know anything about the types it holds. I just want to query the generic class for information it has regardless of what type it has been made with. AKA you get a List and want to know how long it is, you do not know or care what it is a list of.

I could just create a custom event, but I was wondering if there was some other more strait-forward way I do not know about.

Zaccheus
August 27th, 2007, 06:16 PM
I'm not sure what you mean.



Could you achieve that by JobManager implementing a non-generic interface?

For example:


interface IJobManager
{
Int32 JobCount
{
get;
}
}

trenches
August 27th, 2007, 06:39 PM
Did you consider having a nested type (say, a struct) inside JobManager to expose reportable state whch you could then pack inside an EventArgs subclass to go with that custom event you are trying to avoid! Then you can avoid having to cast anything and get all the interesting data you want.

DeepT
August 28th, 2007, 09:25 AM
Yes, I did consider that. That was the last line in my last post. I was just wondering if there was a way to cast an object as a generic type, which there doesn't seem to be.

IE:
From the generic instance, I fire an event:
MyEvent(this, null);

In the handler it might be something like this:

MyEventHandler(sender, eventargs e)
{
JobManager M as sender;
// or
JobManager M = (JobManager)sender;

Console.Writeline("Jobs Running:{0}", M.JobsRunning.ToString());
}

Zaccheus
August 28th, 2007, 09:31 AM
Did you not see my post #15 ?

andreasblixt
August 28th, 2007, 10:00 AM
You can cast generic types:
using System;

namespace Test
{
class BaseClass
{
string value;
public string Value
{
get { return value; }
set { this.value = value; }
}
}

class ChildClass : BaseClass { }

static class Manager<T>
where T : BaseClass, new()
{
public static string GetValue(object instance)
{
if (!(instance is T))
throw new ArgumentException("GetValue only accepts instances of " + typeof(T) + ".", "instance");

T t = (T)instance;
return t.Value;
}

public static T Build(string value)
{
T instance = new T();
instance.Value = value;
return instance;
}
}

class Program
{
static void Main(string[] args)
{
BaseClass test = Manager<ChildClass>.Build("Hello");

Console.WriteLine(Manager<ChildClass>.GetValue(test));
Console.ReadKey(true);

Console.WriteLine(Manager<ChildClass>.GetValue("Hello"));
Console.ReadKey(true);
}
}
}

If your type implements IConvertible you can also do the following:
T value = (T)Convert.ChangeType(instance, typeof(T));

Edit
I changed the generics to the Build() method only, which might reflect what you're trying to achieve more (I'm not 100% sure what you want tho):
using System;

namespace Test
{
class BaseClass
{
string value;
public string Value
{
get { return value; }
set { this.value = value; }
}
}

class ChildClass : BaseClass { }

static class Manager
{
public static string GetValue(object instance)
{
if (!(instance is BaseClass))
throw new ArgumentException("GetValue only accepts derivatives of BaseClass.", "instance");

BaseClass t = (BaseClass)instance;
return t.Value;
}

public static T Build<T>(string value)
where T : BaseClass, new()
{
T instance = new T();
instance.Value = value;
return instance;
}
}

class Program
{
static void Main(string[] args)
{
BaseClass test = Manager.Build<ChildClass>("Hello");

Console.WriteLine(Manager.GetValue(test));
Console.ReadKey(true);

Console.WriteLine(Manager.GetValue("Hello"));
Console.ReadKey(true);
}
}
}

DeepT
August 28th, 2007, 12:09 PM
I kind of see what you are doing here, but it isn't quite what I was trying to do. I am not intrested in directly accessing the types from the Manager, but accessing the managers without knowing what kind they are.

I have a much better example, I think that may be clearer.

Lets say I have 3 managers
Manager<Alpha>
Manager<Beta>
Manager<Delta>

Now how can I make a List of Managers that will hold all the managers (IE: A List of generic Lists) and then how would I go through the list and foreach manager, call the run method ie: Manager.run(); ?

If I knew how to do that, then I think i would have know how to do what I wanted before, although I have gone to a custom event. Still I will need to create a list of Managers at some point, not knowing thier specifc types, only knowing they will all have a .Run() method which is Part of the Manager class, not the classes they contain.

trenches
August 28th, 2007, 12:25 PM
It sounds like you are inching toward Zaccheus' IJob interface. . .with a run method that the manager may execute on all it's constituents.

DeepT
August 28th, 2007, 01:57 PM
I was thinking about this at lunch, and may have not been getting what was said here.

IF I understand what has been said here, then I can have an instance of JobManager<Job_Alpha> Alpha = new JobManager<Job_Alpha> ();

I then could, say

JobManager<Job_Generic> AJobManager = Alpha;

And from there, I can do anything with AJobManager I could have done with Alpha that doesn't directly involve the generic type.

ie: int N = AJobManager.NumberOfJobs; would accuratly tell me how many jobs are in Alpha

AJobManager.Run(); would be the same thing as doing Alpha.Run();

Correct? If that is so, then I get what you have been saying.

Zaccheus
August 28th, 2007, 02:27 PM
Here is what I had in mind:

using System;
using System.Collections.Generic;

interface IJob
{
void DoSometing();
}

interface IJobManager
{
int JobCount
{
get;
}
}

class JobManager<T> : IJobManager where T : IJob
{
private List<T> jobs = new List<T>();

public void Add(T job) // Simplified example.
{
jobs.Add(job);
}

public int JobCount
{
get { return jobs.Count; }
}
}

class SomeJob : IJob
{
public void DoSometing(){}
}

class OtherJob : IJob
{
public void DoSometing(){}
}

class SomeClass
{
public void SomeMethod()
{
JobManager<SomeJob> someJobManager = new JobManager<SomeJob>();
JobManager<OtherJob> otherJobManager = new JobManager<OtherJob>();


someJobManager.Add(new SomeJob());

OutputJobCount(someJobManager);
OutputJobCount(otherJobManager);
}

// This method does not care what kind of job manager it is dealing with!
void OutputJobCount(IJobManager manager)
{
Console.WriteLine(manager.JobCount);
}
}

Scroll down to see my comment in red.

DeepT
August 28th, 2007, 04:49 PM
Ok, I didnt understand the first post you had made on this subject. I do not understand interfaces very well. What is the difference between an interface and an abstract base class?

Zaccheus
August 28th, 2007, 05:08 PM
An interface can be thought of as a class which has no fields and who's methods & properties are all abstract and public.

Note that in C# a class can only be derived from one base class, but a class can be derived from ('implement') any number of interfaces.

trenches
August 28th, 2007, 05:12 PM
Two major diferences:

1. Abstract classes may have concrete and/or abstract members, interfaces can only have abstract members
2. C# single inharitence applies to abstract classes but not to interfaces

DeepT
August 28th, 2007, 05:34 PM
But then like abstract classes, you can declare a reference / pointer to it, but not instance it, correct?

trenches
August 28th, 2007, 06:12 PM
Yes, no concrete instance for abstracts/interfaces, only polymorphic ones, e.g.,

Can't do: IJob jobInterface = new IJob();
Can do: IJob jobInterface = (IJob)AlphaJob;

Same applies to abstracts just as you pointed out, with one little side note and that is that you can have constructor(s) in an abstract class that may be used by subclasses