C# Generics question

Jaxidian

Platinum Member
Oct 22, 2001
2,230
0
71
twitter.com
How do I pass an input parameter to a generic function to an overloaded method that takes all possible types of T? I've attached a bit of sample code that should demonstrate the problem. What I'm trying to do really isn't printing stuff like this, it's much more complex, but you should understand the problem here.

Trying this gives the "The best overloaded method match for ..." exception though it's quite clear that SpecialPrint has an overloaded method that handles every possible type of T.

Help!!!
 

dwell

pics?
Oct 9, 1999
5,185
2
0
You're sort of abusing generics. The generic types should be used for single-types, or type hierarchies, not disparate types (int, DateTime).

You're better off just doing something less sexy but more sensible like:

 

Jaxidian

Platinum Member
Oct 22, 2001
2,230
0
71
twitter.com
Originally posted by: Chris
You're sort of abusing generics. The generic types should be used for single-types, or type hierarchies, not disparate types (int, DateTime).

You're better off just doing something less sexy but more sensible like:

The actual implementation where I'm implementing this has these working with much more similar types of objects than int and DateTime, that was just the first example I could pull off the top of my head. Essentially our class hierarchy is that we have a DataEntry page with a dynamic list of Questions and each Question has an Answer. Well, this is the case in both our business tier as well as our presentation tier. However, we don't have our presentation tier sharing the same objects as our business tier so we have an integration tier between the two that converts the object from a [let's call it] BusinessQuestion to a PresentationQuestion.

However, this integration tier needs to calculate things based on these answers and sometimes it has a list of BusinessQuestions and sometimes it has a list of PresentationQuestions. These two objects are VERY similar, both have mostly the same properties, and everything. However, because they're in separate tiers (business and presentation), we don't want them to be tightly coupled to each other in those tiers.

So due to the lack of a common interface or base class, I figured Generics would come to the rescue. Apparently I was wrong. :(
 

dwell

pics?
Oct 9, 1999
5,185
2
0
Originally posted by: Jaxidian
However, this integration tier needs to calculate things based on these answers and sometimes it has a list of BusinessQuestions and sometimes it has a list of PresentationQuestions. These two objects are VERY similar, both have mostly the same properties, and everything. However, because they're in separate tiers (business and presentation), we don't want them to be tightly coupled to each other in those tiers.

So due to the lack of a common interface or base class, I figured Generics would come to the rescue. Apparently I was wrong. :(

If the two classes are very similar, with the many of the same properties, why can't you extract a common interface out of them (IQuestion)? It seems like the natural way to go about it.

Generics are very cool for locking down implementations to specific types. Not so much for what you're trying to do.
 

Jaxidian

Platinum Member
Oct 22, 2001
2,230
0
71
twitter.com
Originally posted by: Chris

If the two classes are very similar, with the many of the same properties, why can't you extract a common interface out of them (IQuestion)? It seems like the natural way to go about it.

Generics are very cool for locking down implementations to specific types. Not so much for what you're trying to do.

Because this then directly couples our presentation tier to our business tier. As far as we know, the presentation tier may change from a web app to a winforms app - if this happens we want 0 changes to be required by our business tier. Surely we'll have to rewrite the integration tier but that's what it's there for - to be a very small and isolated chunk of code that needs to be rewritten of something like this happens.

If I did what you're suggesting, IQuestion, then why would I even use generics (ignoring that lists are generic)? If I did that, then I'd just pass in a List<IQuestion> to a non-generic method. So if I understand what you're saying generics are for handling, well, non-generics already handle that without casting and such.
 

dwell

pics?
Oct 9, 1999
5,185
2
0
Originally posted by: Jaxidian
Because this then directly couples our presentation tier to our business tier. As far as we know, the presentation tier may change from a web app to a winforms app - if this happens we want 0 changes to be required by our business tier. Surely we'll have to rewrite the integration tier but that's what it's there for - to be a very small and isolated chunk of code that needs to be rewritten of something like this happens.

I am not sure how creating a common interface would couple the presentation tier to the front end. The presentation tier should be model-driven anyway, so it does not matter what the technology is. Any client is essentially just a presentation layer that displays a representation of the server model.

If I did what you're suggesting, IQuestion, then why would I even use generics (ignoring that lists are generic)? If I did that, then I'd just pass in a List<IQuestion> to a non-generic method. So if I understand what you're saying generics are for handling, well, non-generics already handle that without casting and such.

I think we're out of sync on the whole generics thing. All I am suggesting is to use generics for type-safe methods and collections. Don't use them to do anything exotic like mixing disparate types in a collection.

 

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
Originally posted by: Chris
You're sort of abusing generics. The generic types should be used for single-types, or type hierarchies, not disparate types (int, DateTime).

You're better off just doing something less sexy but more sensible like:

I think the answer is somewhat different here. In the OP's example the method signature...

public static string Foo<T>(List<T> items) where T : int, DateTime { //... }

... produces the error...

'System.DateTime' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter.

... on compilation with the snippet compiler, because int and DateTime are both structures. Structures in C# are implicitly sealed, and sealed types cannot be used in a constraint on a generic method.

If you remove the constraint clause you get the error the OP referenced. I think what is going on here is different from what you suggested. The problem I see is that in the OP's implementation of Foo<T>() he expects the compiler to be able to resolve the call to SpecialPrint(Item) at compile time, however the compiler does not yet know what the actual type of T is.

If you include a call of Foo<T>() for, say, int, then you still get the same error. Why? The compiler should now know that Foo<T>() is being called for int, and should be able to determine that the call to SpecialPrint(Item) is for a context where typeof(Item) == int. But C# generics don't work like C++ templates, in that they are not fully instantiated at compile time. Instead the compiler produces IL with place-holders, and then the JIT translator evaluates the parameters supplied at runtime, and creates the appropriate machine code. If the supplied parameter is a value type, the machine code generated uses native types. If the param is a reference type, the machine code uses Object and generates runtime type checking.

So the key point is that the type of T is not known at compile time, so the call to SpecialPrint(Item) cannot be resolved. The only way around this is to do the checking yourself at runtime, as far as I know. See...

http://msdn2.microsoft.com/en-us/librar...564(vs.80).aspx

Edit: should also mention that Chris' advice about not mixing types in a collection (other than a singly-rooted hierarchy) is solid. Also worth stating that C# generics were, as he said, added primarily to support type-safe collections. You're operating at the edges by trying to use them as a mechanism to get generic dispatching at runtime.
 

Jaxidian

Platinum Member
Oct 22, 2001
2,230
0
71
twitter.com
Originally posted by: Markbnj
Originally posted by: Chris
You're sort of abusing generics. The generic types should be used for single-types, or type hierarchies, not disparate types (int, DateTime).

You're better off just doing something less sexy but more sensible like:

I think the answer is somewhat different here. In the OP's example the method signature...

public static string Foo<T>(List<T> items) where T : int, DateTime { //... }

... produces the error...

'System.DateTime' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter.

... on compilation with the snippet compiler, because int and DateTime are both structures. Structures in C# are implicitly sealed, and sealed types cannot be used in a constraint on a generic method.

If you remove the constraint clause you get the error the OP referenced. I think what is going on here is different from what you suggested. The problem I see is that in the OP's implementation of Foo<T>() he expects the compiler to be able to resolve the call to SpecialPrint(Item) at compile time, however the compiler does not yet know what the actual type of T is.

If you include a call of Foo<T>() for, say, int, then you still get the same error. Why? The compiler should now know that Foo<T>() is being called for int, and should be able to determine that the call to SpecialPrint(Item) is for a context where typeof(Item) == int. But C# generics don't work like C++ templates, in that they are not fully instantiated at compile time. Instead the compiler produces IL with place-holders, and then the JIT translator evaluates the parameters supplied at runtime, and creates the appropriate machine code. If the supplied parameter is a value type, the machine code generated uses native types. If the param is a reference type, the machine code uses Object and generates runtime type checking.

So the key point is that the type of T is not known at compile time, so the call to SpecialPrint(Item) cannot be resolved. The only way around this is to do the checking yourself at runtime, as far as I know. See...

http://msdn2.microsoft.com/en-us/librar...564(vs.80).aspx

Edit: should also mention that Chris' advice about not mixing types in a collection (other than a singly-rooted hierarchy) is solid. Also worth stating that C# generics were, as he said, added primarily to support type-safe collections. You're operating at the edges by trying to use them as a mechanism to get generic dispatching at runtime.

Mark, Great post!

You're right in that my example above produced an error totally irrelavant to what I was actually trying to get - sorry about that. But at least the point got across.

I didn't quite think this through to the level that you did, and now that makes sense. I guess I was hoping/expecting that the compiler would be smart enough to know at compile time whether or not it would know what type it was, and if it didn't, then it would reflectively make the call to the appropriate one at runtime. Yeah, I know, a performance hit, but I would think that this should still be possible. Though, I guess I need to realize this is really v1.0 of generics with Microsoft.

Chris, you're right, we were out of sync. I totally agree, I don't want to have a list of multiple types of objects. If I wanted that, I'd have an arraylist. Or better yet, I'd slap myself and then pass in 2 lists. When I use this, I always pass in either List<BusinessQuestion> or List<PresentationQuestion>.

As for how this would tightly couple the two tiers, well, if they both have to implement the same interface, then that interface is a limiting factor to both tiers. What if that interface changes? Then I have to change the code in both tiers. That is the tight coupling that I want to avoid. Especially since this code is only for the integration tier and really has no impact on either the presentation tier or the business tier code - it's just a translator.

Thanks for all the help and thoughts! :) I guess at this point I'll just check for the type and cast things or write two different methods or something unelegant along those lines unless you have a better idea. I just don't want outside code to require code changes to either the presentation tier nor the business tier.

-Jax