Why do I need to cast to my constrained generic's type?

S

sklett

In the below code I don't understand why I need to cast to TEntryType when
calling EntryObjectReceived. Does the Type constraint of "where TEntryType
: DocEntryBase" pretty much guarantee that I'll be getting a compatible
type?

I've also included the implementation of EventArgs<> below the
EntryAssistantController class.

The only thing I can think of is that EventArgs<> does NOT have a constraint
but wouldn't the compiler be able to tell that e.Data is going to be a
DocEntryBase object considering it's declared in the parameters of the
method? Confused...

I'd appreciate someone cluing me in on this, I don't get it.
-SK

<code>
public class EntryAssistantController<TEntryType> : UIAssistantConstants
where TEntryType : DocEntryBase
{
public void LoadDocument(object sender, EventArgs<DocEntryBase> e)
{
EntryObjectReceived((TEntryType)e.Data);
}

protected virtual void EntryObjectReceived(TEntryType entryObject){ ; }
}


public class EventArgs<T> : EventArgs
{
private T _data;

public EventArgs(T data)
{
_data = data;
}

public T Data
{
get { return _data; }
}
}
</code>
 
P

Peter Duniho

sklett said:
In the below code I don't understand why I need to cast to TEntryType when
calling EntryObjectReceived. Does the Type constraint of "where TEntryType
: DocEntryBase" pretty much guarantee that I'll be getting a compatible
type?

I've also included the implementation of EventArgs<> below the
EntryAssistantController class.

The only thing I can think of is that EventArgs<> does NOT have a constraint
but wouldn't the compiler be able to tell that e.Data is going to be a
DocEntryBase object considering it's declared in the parameters of the
method? Confused...

The relationship is backwards from what you want it to be. Your
constraint requires that the type parameter TEntryType inherit
DocEntryBase. But, it could be any more-derived type.

So, the EntryObjectReceived(TEntryType entryObject) method could require
a type more specific than DocEntryBase. The only guarantee your current
declarations offer is that e.Data is of the type DocEntryBase, which may
or may not be the actual TEntryType type you want.

For example, assume:

class EntryType1 : DocEntryBase { }
class EntryType2 : DocEntryBase { }

Then:

// Initialization is perfectly legal:
EntryAssistantController<EntryType1> foo = …;
EventArgs<DocEntryBase> bar =
new EventArgs<DocEntryBase>(new EntryType2());

// As is this call:
foo.LoadDocument(foo, bar);

But, the above will crash on your cast in the LoadDocument() method,
because bar.Data is of type EntryType2, but your code tries to cast it
to EntryType1.

So, the compiler is really trying to tell you something here: you're
trying to rely on a guarantee that just doesn't exist. Don't put that
cast into the code, because it removes whatever type safety the generic
code would have provided in the first place.

Pete
 
S

sklett

So, the EntryObjectReceived(TEntryType entryObject) method could require a
type more specific than DocEntryBase. The only guarantee your current
declarations offer is that e.Data is of the type DocEntryBase, which may
or may not be the actual TEntryType type you want.

Yeah, I see now that this makes sense. It's not what I want, but I can see
how it could be an issue.

For example, assume:

class EntryType1 : DocEntryBase { }
class EntryType2 : DocEntryBase { }

Then:

// Initialization is perfectly legal:
EntryAssistantController<EntryType1> foo = …;
EventArgs<DocEntryBase> bar =
new EventArgs<DocEntryBase>(new EntryType2());

// As is this call:
foo.LoadDocument(foo, bar);

But, the above will crash on your cast in the LoadDocument() method,
because bar.Data is of type EntryType2, but your code tries to cast it to
EntryType1.

So, the compiler is really trying to tell you something here: you're
trying to rely on a guarantee that just doesn't exist. Don't put that
cast into the code, because it removes whatever type safety the generic
code would have provided in the first place.

I don't see how I can make this design work (not the first time!). I was
thinking that polymorphism would allow me to accomplish what I had above.
From your example I see how easily it can be broken by casting. I don't
usually ask for design suggestions here but I would appreciate any ideas you
may have. My grasp of generics and polymorphism is apparently lacking right
now.

Thanks for the explanation, I'll keep looking at this and see how I can make
it do what I want, if at all.

-SK
 
P

Peter Duniho

sklett said:
[...]
I don't see how I can make this design work (not the first time!). I was
thinking that polymorphism would allow me to accomplish what I had above.
From your example I see how easily it can be broken by casting. I don't
usually ask for design suggestions here but I would appreciate any ideas you
may have. My grasp of generics and polymorphism is apparently lacking right
now.

Design questions are fine, IMHO. They're less likely to get good
answers, because it's harder to explain the design goal with enough
detail for people to really understand the needs, and yet be concise
enough for people to not run out of patience or time before they've
understood the design goal. But it's always worth a try. :)

Unfortunately, there's not much in your first post to really illustrate
what you're trying to do. The code you posted certainly has things in
it that don't seem to make sense, but I'm unable to extrapolate from
those irregularities what the real goal is here.

I will point out that the LoadDocument() method _looks_ like an event
handler, yet is public. Which is unusual for an event handler. And why
is the class generic except for that one method? Why not just make the
"e" argument for the LoadDocument() use the type parameter, so that the
argument always matches?

What is it exactly you're trying to do? Why does the code you posted
look the way it does? What behaviors do you need this class to expose
to the code that uses it? Why is it generic in the first place? What
other details do you think you should share that would help explain what
your goals are here?

Pete
 
S

sklett

Design questions are fine, IMHO. They're less likely to get good answers,
because it's harder to explain the design goal with enough detail for
people to really understand the needs, and yet be concise enough for
people to not run out of patience or time before they've understood the
design goal. But it's always worth a try. :)

I appreciat the great attitude and willingness.

I will point out that the LoadDocument() method _looks_ like an event
handler, yet is public. Which is unusual for an event handler. And why
is the class generic except for that one method? Why not just make the
"e" argument for the LoadDocument() use the type parameter, so that the
argument always matches?

This is my fault, I was trying to keep it concise but as you suggested it
can have the opposite effect.
I'll take a stab at your other questions below.

What is it exactly you're trying to do? Why does the code you posted look
the way it does? What behaviors do you need this class to expose to the
code that uses it? Why is it generic in the first place? What other
details do you think you should share that would help explain what your
goals are here?

I'm trying to reduce duplication between 10 or so sibling concrete classes
by moving as much shared functionality into a base class as possible. Even
fields and properties. Each of my concrete Assistant classes also have a
specific concrete DataEntryObject and they each maintain a single reference
to one of these objects. So, Form100Assistant would have a ref. to
Form100EntryObject, etc. Additionally there is a single event that all the
concrete Assistants subscribe to and I wanted to move that to the base class
then call an virtual method. Again, trying to avoid having the same 6 lines
of event handling glue code in each concrete class.

My plan was that my concrete Assistant class would inherit a generic
AssistantBase class and pass along it's specific DataEntryObject type so the
base class could handle it.

I suspect I have already started to confuse things. I will take another
stab with code and hopefully this will paint a better picture.

<code>

public abstract class EntryAssistantController<TEntryType> :
UIAssistantController
where TEntryType : DocEntryBase
{
protected TEntryType EntryObject { get; private set; }

[EventSubscription(EventTopicNames.LoadDocument,
ThreadOption.Publisher)]
public void LoadDocument(object sender, EventArgs<DocEntryBase> e)
{
// I don't understand why I need to case to the TEntryType. Why
the hell am I using
// a generic class then?!
EntryObject = (TEntryType)e.Data;
EntryObjectReceived(EntryObject);
}

protected virtual void EntryObjectReceived(TEntryType entryObject){ ; }
}

public class Form110EntryAssistantController :
EntryAssistantController<Doc110Entry>
{
// Other guts ripped out for this example...

protected override void EntryObjectReceived(Doc110Entry entryObject)
{
// Do stuff with the entry object
// I realize that I also have access to the Document property on
the base class
// but please just pretend this is some method that is taking an
arbitrary Doc110Entry
// instance. It's the Type not the instance that is important...
}
}

</code>

My design goals are simply to combine duplicate code sections into a base
class. I just took another look at this and it passed my "Well, is this a
good idea?" test so I'm eager to see what you think of it ;0)

-SK
 
P

Peter Duniho

sklett said:
[...]
I'm trying to reduce duplication between 10 or so sibling concrete classes
by moving as much shared functionality into a base class as possible. Even
fields and properties. Each of my concrete Assistant classes also have a
specific concrete DataEntryObject and they each maintain a single reference
to one of these objects. So, Form100Assistant would have a ref. to
Form100EntryObject, etc. Additionally there is a single event that all the
concrete Assistants subscribe to and I wanted to move that to the base class
then call an virtual method. [...]

I don't understand the above description. If it's actually the same
event, why does it pass different types as the EventArgs<DocEntryBase>?
That seems like a problematic design, likely to cause maintenance
problems.

On the other hand, if you don't really mean it's literally the same
event, then I would think that there's a way to get the correct type for
the handler, rather than the base type.

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.

Pete

p.s. By the way, as far as me having replied to other messages but not
this one, I have to prioritize. Some questions are simpler to answer
than others, and so the more difficult ones sometimes have to wait to
get the attention they deserve. If you feel you're being ignored,
sometimes it might make sense to follow up your post in the newsgroup
with another post, just to double-check that your previous one was seen.
But even there, I'd say 24-48 hours is a much more reasonable amount
of time to wait before getting worried than, say…nine or ten hours.
 
S

sklett

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.

Here is the code, I hope this is short enough.

<code>
using System;
using System.ComponentModel;

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main called");

// Assume that we received the documentTypeID from another part of
the system. It
// indicates what type of document we need to work with.
int jobTypeID = 1;

var controller = new Form100JobController();

// Kick off worker thread to load up the required data needed...
BackgroundWorker _worker = new BackgroundWorker();
_worker.DoWork += delegate(object sender, DoWorkEventArgs e)
{
// Get our specific entry object
EntryObjectBase entryObject =
EntryObjectFactory.Create((int)e.Argument);

// Simulate delay hitting DB and other services...
Console.WriteLine("Worker thread hitting database and services,
loading data...");
System.Threading.Thread.Sleep(2500);
Console.WriteLine("Worker thread finished loading data and about
to broadcast " +
"result to controllers");

// The following would normally be handled through the eventing
framework I'm using
// but it's way too much work to include that here and really
doesn't matter for this
// example.
controller.OnEntryObjectReady(null, new
EventArgs<EntryObjectBase>(entryObject));
};

_worker.RunWorkerAsync(jobTypeID);

Console.Read();
}
}

#region Factories
class EntryObjectFactory
{
public static EntryObjectBase Create(int
dataUsedToDetermineWhatToCreate)
{
if (dataUsedToDetermineWhatToCreate == 1)
{
Console.WriteLine("EntryObjectFactory.Create() returning new
Form100Entry instance");
return new Form100Entry();
}
else if (dataUsedToDetermineWhatToCreate == 2)
{
Console.WriteLine("EntryObjectFactory.Create() returning new
Form200Entry instance");
return new Form200Entry();
}


return null;
}
}
#endregion

#region SomeJobController
class JobController<TEntryType> where TEntryType : EntryObjectBase
{
protected TEntryType EntryObject { get; private set; }

protected virtual void EntryObjectReady(TEntryType entryObject) { ; }

// Event handler for the "EntryObjectLoaded" event which is coming from
a completely diff.
// subsystem on a background thread.
public void OnEntryObjectReady(object sender, EventArgs<EntryObjectBase>
e)
{
Console.WriteLine("JobController.OnEntryObjectReady received {0}
EntryObject",
e.Data.GetType().Name);

EntryObject = e.Data as TEntryType;
EntryObjectReady(e.Data as TEntryType);
}
}

// This job-specific controller knows that type of entry object it will be
working with and it's
// passing that type to the base controller.
class Form100JobController : JobController<Form100Entry>
{
public Form100JobController()
{
Console.WriteLine("Form100JobController created and on idle until it
receives " +
"entry object");
}

protected override void EntryObjectReady(Form100Entry entryObject)
{
// Do stuff now that we have the entry object from the base class
Console.WriteLine("Form100JobController received concrete
EntryObject {0} from " +
"JobController", entryObject.GetType().Name);
}
}

class Form200JobController : JobController<Form200Entry>
{
public Form200JobController()
{
Console.WriteLine("Form200JobController created and on idle until it
receives " +
"entry object");
}

protected override void EntryObjectReady(Form200Entry entryObject)
{
// Do stuff now that we have the entry object from the base class
Console.WriteLine("Form200JobController received concrete
EntryObject {0} from " +
"JobController", entryObject.GetType().Name);
}
}
#endregion

#region EntryObjects
class EntryObjectBase
{
public int DocumentID { get; set; }
}

class Form100Entry : EntryObjectBase
{
public int Form100SpecificData { get; set; }
}

// The following is just to illustrate that we have multiple concrete
types...
class Form200Entry : EntryObjectBase
{
public int Form200SpecificData { get; set; }
}
#endregion

#region EventArgs
[Serializable]
public class EventArgs<T> : EventArgs
{
private T _data;

public EventArgs(T data)
{
_data = data;
}

public T Data
{
get { return _data; }
}
}
#endregion
p.s. By the way, as far as me having replied to other messages but not
this one, I have to prioritize. Some questions are simpler to answer than
others, and so the more difficult ones sometimes have to wait to get the
attention they deserve. If you feel you're being ignored, sometimes it
might make sense to follow up your post in the newsgroup with another
post, just to double-check that your previous one was seen. But even
there, I'd say 24-48 hours is a much more reasonable amount of time to
wait before getting worried than, say.nine or ten hours.

I'm sorry it came off as impatient, I was really just checking to see if you
missed the message. I get it though, I will use the NG to bubble up a
message.

Thanks again for your time and replies, I appreciate it.

-SK
 
P

Peter Duniho

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top