views:

96

answers:

3

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.

+5  A: 

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...

Matthew Whited
I'm still waiting for the day I can say `where T.Parse(string)`
Matthew Whited
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).
Dan Tao
@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
@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.
Matthew Whited
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.)
Matthew Whited
@Matthew Whited: Constraining by static method seems kind of scary to me. I could imagine such static methods could have side effects, adding weird execution paths to code.
Brian
@Brian, It could... but so could everything else we do. But as I pointed out right now it doesn't even matter because there is no way you could do it natively in the CLR (same goes for multiple inheritance.) It could also be nice if you could have an interface such as INumeric or have an interface define required static methods (such as IParse) or constructor signatures. It would also be pretty cool if we had more operator overloads and if they could be virtual and native. (Right now operator overloads are really hacks that get redirected to the method via the compiler.)
Matthew Whited
Heck, it would be pretty compelling (though I imagine rather difficult) to allow method signatures to vary by the method return (in VB.Net/C# due to it being supported by the CLR and CIL.)
Matthew Whited
A: 

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.

Ruben
@Ruben: 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 Tao
@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.
Ruben
+5  A: 

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

Eric Lippert