sklett said:
Unfortunately, without a code example complete enough to include the
entire set of relationships between the code involved, including the
declaration and implementation of the event itself, it's still difficult
to suggest a solution.
Please provide more details with respect to the event itself of which
you're trying to generalize the handling. Ideally, you'll provide a
concise-but-complete code example for the purpose, but at a minimum there
needs to be enough code to fully illustrate what exactly the event is
passing when it's raised, and how the event decides what to pass.
Understood, I was hoping it would be enough but I can see that it's not. I
have created a working example that illustrates the basic design I'm
shooting for. This application does nothing except print some messages to
the Console. It also doesn't make much sense how I'm creating and using
factories in the Main method but I really can't make a simple and concise
application and also have every aspect of it easily understood. Please just
assume that in my application different types concrete JobControllers are
being created. [...]
For better or worse, the code example you posted still doesn't include
the specific details about the event I asked for. You seem to indicate
you don't feel that's important, even though to me it seems like it
would be useful information. But, I'll try to offer comments based on
what you did share.
The bottom line here is that your design seems flawed to me, at least
relative to your stated goals. You've got code that has a parametric
factory method on the one hand, but then explicitly instantiates a given
controller type on the other hand. If you stick with that design,
there's always going to be a barrier between your generic type and the
concrete types.
If you're willing to make some design changes, then it's possible a
solution can be found.
One possibility is to delegate the creation of the EntryObjectBase
object to the generic type, rather than going through the factory. That
way, you always get the explicit type, rather than the base type. This
would require either using type inference with "var" or explicitly
declaring your local variable in the anonymous method to be of the
correct type (which I don't think should be a problem, since the
controller is already explicitly typed).
In that model, you can either also create the EventArgs<T> explicitly
typed (again, since the controller type is known, shouldn't be a
problem), or you can use generic type inference to make an EventArgs<T>
factory class.
Here are some excerpts from your original example demonstrating the
above (edited for brevity):
static void Main(string[] args)
{
var controller = new Form100JobController();
BackgroundWorker _worker = new BackgroundWorker();
_worker.DoWork += delegate(object sender, DoWorkEventArgs e)
{
var entryObject = controller.Create();
controller.OnEntryObjectReady(null,
EventArgsFactory.Create(entryObject));
};
_worker.RunWorkerAsync(jobTypeID);
}
class JobController<TEntryType> where TEntryType : EntryObjectBase, new()
{
protected TEntryType EntryObject { get; private set; }
protected virtual void EntryObjectReady(TEntryType entryObject)
{ }
public void OnEntryObjectReady(
object sender, EventArgs<TEntryType> e)
{
EntryObject = e.Data;
EntryObjectReady(e.Data);
}
public TEntryType Create()
{
Console.WriteLine("EntryObjectFactory.Create() returning new
{0} instance", typeof(TEntryType).Name);
return new TEntryType();
}
}
And instead of the other factory you posted, this simple one, just so
type inference can be used instead of explicitly writing the type (note
that this doesn't get you out of having to have the type declared in the
code…the type still needs to be known statically by the compiler when
this method is called):
public class EventArgsFactory
{
public static EventArgs<T> Create<T>(T t)
{
return new EventArgs<T>(t);
}
}
Another option might be to have your factory create the controller as a
complete, generic type unto itself, without the derived classes. But I
have the impression that you want customized functionality in the
derived classes; you could provide that via a delegate instead, but it's
not clear there would be much value in taking that approach.
Basically, if you want the generics to work together, you will need to
couple the controller and event argument types more closely, at least in
terms of usage in their respective client code. I personally find the
design you've presented odd, in that you've got one piece of code that
hard-codes the type being used, but another that is using a factory
method taking an selection argument. It seems like it would make more
sense to have everything go through some kind of common factory, or
otherwise be tied more closely.
All that said, in the end I don't think that casting is the worst thing
that you could do. The thing that is preventing the generics from
working in the code you posted is your factory method that always
returns the base type. As long as you use that kind of factory method,
you will be stuck casting _somewhere_. You can cast inside your generic
type, or you can cast before you call the generic type, but you'll have
to cast some place. That's the consequence of not carrying the full
generic type throughout the code using it.
If you _do_ decide you want to avoid the casting, then whatever the
solution you come up with, it will necessarily involve changing the
logic that creates your EntryObjectBase object, so that instead of going
through the base-typed factory method, you always have a fully-typed,
derived object. I've shown one possible approach, but that's far from
necessarily being the best approach. But without a code example that is
clearer about what has to remain invariant in the design and what can be
changed, it's not really possible to offer anything more specific.
Pete