13

Consider this trivial function:

public static bool IsPositive(IComparable<int> value) { return value.CompareTo(0) > 0; } 

Now, if I pass an int to this method, it gets boxed. Wouldn't it therefore be better to define the above method as follows?

public static bool IsPositive<T>(T value) where T : IComparable<int> { return value.CompareTo(0) > 0; } 

Using a generic constraint in this way, I can achieve exactly the same functionality as the code above, with the added benefit that no boxing is necessary (since a call to IsPositive<int> accepts a parameter of type int).

The example code above is clearly quite pointless. But my broader question is: wouldn't it always make sense to define methods in the latter way (using a generic constraint rather than having a parameter of some interface type), to avoid the potential boxing of value types?

I suspect that the answer is likely to be "yes, but it requires more typing and in many cases encountering a value type will be very unlikely, such as when a method accepts some IEnumerable<T>." But I'm wondering if there's a greater difference between these approaches that is escaping me at the moment.

10
  • Isn't boxing/specialization just about generic types and not functions? Commented Aug 19, 2010 at 20:13
  • @Dario: I'm not sure what you're asking. If a value type is passed to a method that accepts an interface type parameter, that value is boxed. This is why it's possible to set the local parameter to null from within the method. With the generic approach, on the other hand, the local parameter within IsPositive<T> is of type T which may be a value type or reference type. Does that make sense? Commented Aug 19, 2010 at 20:20
  • @Dan - In the generic case, valuetypes still need to be boxed. IL allows a uniform way of using the type with the constrained instruction, but when compiled to machine code, it's no different from the previous boxing method - there's nothing to gain. Commented Aug 19, 2010 at 20:27
  • @Mark H: If what you say is true then that's clearly the most compelling reason of all. Are you sure, though? Can you demonstrate this? Commented Aug 19, 2010 at 20:33
  • Perhaps not, I'll benchmark the two :/ Commented Aug 19, 2010 at 20:49

3 Answers 3

8

One issue would be with the fact that the generic constrain isn't really part of the signature. If you have ...

static T Method<T>(T value) where T : ICompareable<int> 

... and ...

static T Method<T>(T value) where T : IEnumerable<int> 

... the compiler wouldn't have a way to know which is which.

And to invoke Eric Lippert...

Sign up to request clarification or add additional context in comments.

8 Comments

I'm still waiting for the day I can say where T.Parse(string)
Ah, very good point. OK, I knew there had to be at least something that wasn't occurring to me. But I await more answers (because I'm sure there's more I haven't taken into consideration as well).
@Matthew Whited: I'm confused. where T.Parse(string) seems like a rather poor idea for a constraint. Or are you waiting for it to show up in a question?
@Brian, the idea would be to allow constraining a generic parameter based on a static method. Then you could use a method (such as .Parse(...)) on the types based into your method. This would allow you to write generic adapters that didn't require reflection.
BTW, as it is right now this idea would require changes to the CLR such as something similar to a virtual lookup table for static methods. Right now static methods calls are determined at compile time and as such cannot be used with static methods (you could try to hand code the IL but it would throw an exception at runtime because it wouldn't be able to resolve the method call.)
|
7

There was some confusion in the comments to the question regarding whether or not the call to the method induces a boxing after the argument is passed.

When you make a call to a virtual method on an expression whose type is a type parameter with a constraint on it, the C# compiler emits a constrained.callvirt instruction. As one would hope, this does the right thing; the boxing only happens when absolutely necessary.

For details regarding the precise boxing semantics of constrained virtual calls, read the documentation:

http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained.aspx

Comments

0

Another issue would be when the generic constraint is on a parameterized type's generic type parameters, such as

static bool AreAllTheSame<T>(IEnumerable<T> something) where T : IEquatable<T> 

It is not always possible to convert the generic type parameter this way, unless you introduce a second type parameter like this:

static bool AreAllTheSame<S, T>(S something) where S : IEnumerable<T> where T : IEquatable<T> 

That just doesn't look right.

2 Comments

But it's totally fine (except that I think you need to change your code to read where S : IEnumerable<T> where T : IEquatable<T> -- two where constraints, rather than one with a comma in it). You're saying it's not an option because it "doesn't look right"?
@Dan: I've fixed that. The reason I say it doesn't look right is because you need an extra type parameter just to appease the constraints engine. If you look at the method's signature and intent, one of the two parameters is completely redundant. Also, this way T is almost hidden from the API consumer, even though it's (IMHO) the most relevant type parameter.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.