.NET Forum / .NET Framework / CLR / August 2004
enumerations as parameters, VB, and the value 0
|
|
Thread rating:  |
Plausible Indirection - 10 Aug 2004 23:18 GMT I'm having some trouble with the value 0 and how VB is resolving its type when it is used as a parameter (to a constructor in this case). The same problem does not reproduce in C#; the correct constructor is always called.
Sorry for the length, but the best description is simplified code:
Module Module1
Enum Bar Bar0 = 0 Bar1 = 2 Bar2 = 4 End Enum
Structure Foo Dim field1 As Int32 Dim state1 As Bar Dim state2 As Bar
Public Sub New(ByVal f1 As Int32, ByVal b2 As Bar) field1 = f1 state1 = Bar.Bar0 state2 = b2 End Sub
Public Sub New(ByVal s1 As Bar, ByVal s2 As Bar) field1 = 0 state1 = s1 state2 = s2 If state1 >= state2 Then Throw New ArgumentException End If End Sub
End Structure Sub Main() Try Dim dt As New Foo(1, Bar.Bar0) System.Console.WriteLine("Problem did not reproduce. value of 1 was accepted.")
dt = New Foo(2, Bar.Bar0) System.Console.WriteLine("Problem did not reproduce. value of 2 was accepted.")
dt = New Foo(0, Bar.Bar0) System.Console.WriteLine("Problem did not reproduce. value of 0 was accepted.")
Catch ex As System.Exception System.Console.WriteLine("Problem reproduced. value of 0 caused this System.Exception." + vbCrLf)
System.Console.WriteLine("Error: " + ex.ToString) End Try
End Sub
End Module
The values 1 and 2 work. On the last constructor call, VB runtime resolves this to the enum-enum constuctor rather than the the int-enum constructor. Is this expected? Why should any language use type resolution that is value dependent (except for range problems, which this isn't).
Any takers?
Thanks, Chris
Jay B. Harlow [MVP - Outlook] - 11 Aug 2004 16:21 GMT Chris, I will see what I can find out as to Why.
I know resolving to the Enum overload is how it works, I just don't remember the why.
More later Jay
> I'm having some trouble with the value 0 and how VB is resolving its > type when it is used as a parameter (to a constructor in this case). [quoted text clipped - 67 lines] > Thanks, > Chris JD - 11 Aug 2004 19:32 GMT Its not the runtime but the compiler. Look at the IL output from the VB.NET compiler versus C# compiler. C# does it correct but VB.NET does not. Bug?
> I'm having some trouble with the value 0 and how VB is resolving its > type when it is used as a parameter (to a constructor in this case). [quoted text clipped - 67 lines] > Thanks, > Chris Jay B. Harlow [MVP - Outlook] - 15 Aug 2004 06:17 GMT Chris,
> The values 1 and 2 work. On the last constructor call, VB runtime > resolves this to the enum-enum constuctor rather than the the int-enum > constructor. Is this expected? Why should any language use type > resolution that is value dependent (except for range problems, which > this isn't). Resolving to the enum-enum constructor over the int-enum constructor is the expected behavior in VB.NET 2002 & 2003. As the literal 0 (remember that it is a literal 0, not an integer 0!) widens to enumerated types, it also widens to an Integer (short, long). The Enumerated type also widens to an Integer.
The Enumerated type is more specific then an Integer type, hence the Enum-Enum constructor is going to be selected.
I have not tried this in VB.NET 2005 Beta 1 to see if the same rules apply or not... (VS.NET 2005 will be available sometime in 2005).
Hope this helps Jay
> I'm having some trouble with the value 0 and how VB is resolving its > type when it is used as a parameter (to a constructor in this case). [quoted text clipped - 67 lines] > Thanks, > Chris JD - 15 Aug 2004 15:20 GMT Looking at the VB compiler IL output and the difference between the call with 2 versus the call with 0.
-- 2 IL_0013: ldloca.s _Vb_t_record_0 IL_0015: ldc.i4.2 IL_0016: ldc.i4.0 IL_0017: call instance void Module1/Foo::.ctor(int32,valuetype Module1/Bar)
-- 0 IL_0028: ldloca.s _Vb_t_record_0 IL_002a: ldc.i4.0 IL_002b: ldc.i4.0 IL_002c: call instance void Module1/Foo::.ctor(valuetype Module1/Bar, valuetype Module1/Bar)
Why does the compiler choose to widen 0 to enumerated type but chooses to widen 2 to the integer type?
JD
> Chris, > > The values 1 and 2 work. On the last constructor call, VB runtime [quoted text clipped - 89 lines] > > Thanks, > > Chris Jay B. Harlow [MVP - Outlook] - 15 Aug 2004 17:05 GMT JD,
> Why does the compiler choose to widen 0 to enumerated type but chooses to > widen 2 to the integer type? As I stated the enumerated type is more specific then an Integer!
The compiler looks for the most specific overload and selects it. Seeing as the enum is more specific it gets picked!
Enum Bar is an "Integer", by virtue of all Enums are implemented in terms on an ordinal value type (Byte, Short, Integer, Long).
The Literal 0 could be a Byte, Short, Integer, Long or Enum Bar. The compiler looks for the most specific type that the literal 0 fits into of the overloaded methods. Enum Bar is the most specific, hence it gets used.
Take a step back and consider only Object & Integer overloads. Integer is more specific then Object so Integer is selected.
Same with Integer & Enum, Enum is more specific then Integer, so Enum is selected.
Hope this helps Jay
> Looking at the VB compiler IL output and the difference between the call > with 2 versus the call with 0. [quoted text clipped - 113 lines] > > > Thanks, > > > Chris JD - 15 Aug 2004 17:36 GMT Maybe I'm confused. The two calls below. 2 is not a literal but 0 is?
dt = New Foo(2, Bar.Bar0)
dt = New Foo(0, Bar.Bar0)
> JD, > > Why does the compiler choose to widen 0 to enumerated type but chooses to [quoted text clipped - 140 lines] > > > > Thanks, > > > > Chris JD - 15 Aug 2004 17:46 GMT Oops pulled the trigger too quick.
> > The Literal 0 could be a Byte, Short, Integer, Long or Enum Bar. The > > compiler looks for the most specific type that the literal 0 fits into of > > the overloaded methods. Enum Bar is the most specific, hence it gets used. Understood. I don't have a problem with the call with literal 0 finding the enum overload. Its the call with literal 2 not finding the enum overload. Doesn't the call with the literal 2 fall under the same rules as stated above call with 0?
> Maybe I'm confused. The two calls below. 2 is not a literal but 0 is? > [quoted text clipped - 159 lines] > > > > > Thanks, > > > > > Chris Jon Skeet [C# MVP] - 16 Aug 2004 09:04 GMT > Oops pulled the trigger too quick. > [quoted text clipped - 8 lines] > Doesn't the call with the literal 2 fall under the same rules as stated > above call with 0? No - there's no implicit conversion between 2 and an enumeration type, whereas there *is* an implicit conversion between 0 and any enumeration type.
For instance:
Dim x as HttpStatusCode x = 0 ' Valid x = 1 ' Invalid
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
Jay B. Harlow [MVP - Outlook] - 16 Aug 2004 06:05 GMT JD, Me bad. I miss read your question.
You are correct 2 is also a literal.
The literal 0 is treated special in that it will bind to the Enum. It binds to the enum as 0 is the "default" value for the Enum. (It is the value that is assigned when you do not initialize the Enum, or you assign Nothing to the Enum).
The literal 2 does not follow the same rule as a literal 0.
Because literal 0 is used as the "default" value for an Enum, I normally define a None value on my enums...
Enum Bar None = 0 Bar0 = 1 Bar1 = 2 Bar2 = 4 End Enum
Hope this helps Jay
> Maybe I'm confused. The two calls below. 2 is not a literal but 0 is? > [quoted text clipped - 159 lines] > > > > > Thanks, > > > > > Chris JD - 16 Aug 2004 12:29 GMT Aaah...default value for Value types. Thanks Jay and John.
> JD, > Me bad. I miss read your question. [quoted text clipped - 199 lines] > > > > > > Thanks, > > > > > > Chris Plausible Indirection - 17 Aug 2004 19:25 GMT I thought the common type system of .Net was supposed to allow you to expect the same behavior of the types across the languages, but that isn't the case here. VB does one thing and C# another.
-Chris
> Aaah...default value for Value types. Thanks Jay and John. Jon Skeet [C# MVP] - 17 Aug 2004 19:46 GMT > I thought the common type system of .Net was supposed to allow you to > expect the same behavior of the types across the languages, but that > isn't the case here. VB does one thing and C# another. In what way, exactly? There may be something, but I'm not seeing it here. There's an implicit conversion between 0 and enums in C# too, and the default value for an enum type is 0. Or are you talking about overload resolution?
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
Plausible Indirection - 18 Aug 2004 15:54 GMT Well, overload resolution is determined by the parameter types, right? You'll have a hard time convincing me that a 0 should not be of the same type as 0.
The same program translated to C# does not give an error; or rather
dt = new Foo(0, Bar.Bar0);
resolves to
public Foo(Int32 f1, Bar b2)
So, a literal 0 in C# becomes an integer and a literal 0 in VB becomes some kind of generic enumeration. I say generic enumeration because if I add an enum Sna that is basically the same as Bar and
Public Sub New(ByVal s1 As Sna, ByVal s2 As Bar)
...
dt = New Foo(CInt(0), Bar.Bar0)
I get a compiler error that
error BC30521: Overload resolution failed because no accessible 'New' is most specific for these arguments: 'Public Sub New(s1 As Sna, s2 As Bar)': Not most specific. 'Public Sub New(s1 As Bar, s2 As Bar)': Not most specific.
In contrast, the same thing in C# works; or rather, since 0 defaults to an integer, the overload to use is never in question.
It seems that the creators of VB have lumped enumerations in with integers, just a more specific variety, most likely because the underlying implementation has the storage characteristic of an integer. In OO programming, I prefer that the underlying storage or implementation is entirely unknown on the surface. Playing with some code,
Bar test; test = Bar.Bar0 + Sna.Sna0;
gives a compile error; whereas,
Dim test As Bar test = Bar.Bar0 + Sna.Sna0
assigns the value Bar1 to test.
It seems sloppy to me that two enumerations that are entirely different, say Apples and Oranges, can be added or compared. For more language differences between enum types compare
Boolean test; test = (Bar.Bar0 == Sna.Sna0);
which gives a compiler error, with
Dim test As Boolean test = (Bar.Bar0 = Sna.Sna0)
which assigns the value True to test.
That's about all I have to say on the subject. I concede that that's the way it is and when some customer runs into this problem while trying to use my type, I'll just have to tell them to assign the value 0 to a variable before using it in a constructor.
-Chris
> > I thought the common type system of .Net was supposed to allow you to > > expect the same behavior of the types across the languages, but that [quoted text clipped - 4 lines] > the default value for an enum type is 0. Or are you talking about > overload resolution? Jon Skeet [C# MVP] - 18 Aug 2004 17:23 GMT > Well, overload resolution is determined by the parameter types, right? > You'll have a hard time convincing me that a 0 should not be of the > same type as 0. <snip>
> In contrast, the same thing in C# works; or rather, since 0 defaults > to an integer, the overload to use is never in question. It does, actually. For instance:
using System;
enum TestEnum { Foo=1 }
public class Test { static void Main() { DoSomething(0); } static void DoSomething(TestEnum x) { } }
compiles fine, so clearly DoSomething(TestEnum x) is an appropriate method to call - it's just that C#'s overload resolution is defined slightly differently. I don't think that's either particualrly surprising or a problem.
> It seems that the creators of VB have lumped enumerations in with > integers, just a more specific variety, most likely because the [quoted text clipped - 12 lines] > > assigns the value Bar1 to test. Only when you don't have Option Strict On. There are any number of things which are pretty horrible when you don't have Option Strict On - this is far from the worst of them.
> It seems sloppy to me that two enumerations that are entirely > different, say Apples and Oranges, can be added or compared. For more [quoted text clipped - 9 lines] > > which assigns the value True to test. Okay, that one still does compile with Option Strict On, unfortunately.
> That's about all I have to say on the subject. I concede that that's > the way it is and when some customer runs into this problem while > trying to use my type, I'll just have to tell them to assign the value > 0 to a variable before using it in a constructor. You could try telling them to turn Option Strict On too, if they're seeing problems like the first one.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
cody - 18 Aug 2004 08:06 GMT This has nothing to do with the CTS but only with the language used.
-- cody
Freeware Tools, Games and Humour http://www.deutronium.de.vu || http://www.deutronium.tk
> I thought the common type system of .Net was supposed to allow you to > expect the same behavior of the types across the languages, but that [quoted text clipped - 3 lines] > > > Aaah...default value for Value types. Thanks Jay and John. cody - 16 Aug 2004 08:18 GMT > As I stated the enumerated type is more specific then an Integer! Why is it more specific? They both inherit from valuetype. If I were compiler designer I wouldn't allow 0 to implicitly cast to an enum because not all enums have a valid 0 value! And even if I would allow this, I wouldn't allow a call for Foo(0) if there is an int and enum overload. I would raise a compiler error stating that 0 must be explicitly casted to the specific enum type, otherwise we have an ambiguity.
-- cody
Freeware Tools, Games and Humour http://www.deutronium.de.vu || http://www.deutronium.tk
Jon Skeet [C# MVP] - 16 Aug 2004 09:06 GMT > > As I stated the enumerated type is more specific then an Integer! > > Why is it more specific? They both inherit from valuetype. It's more applicable because there is a widening conversion from the enum to Integer, but no widening conversion from Integer to enum.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
cody - 16 Aug 2004 09:24 GMT > > Why is it more specific? They both inherit from valuetype. > > It's more applicable because there is a widening conversion from the > enum to Integer, but no widening conversion from Integer to enum. what do you mean with widening conversion? int and enum are the same size. But now the question arises what if you have enum Test: long{A,B,C} ?
-- cody
Freeware Tools, Games and Humour http://www.deutronium.de.vu || http://www.deutronium.tk
> > > As I stated the enumerated type is more specific then an Integer! > > [quoted text clipped - 7 lines] > http://www.pobox.com/~skeet > If replying to the group, please do not mail me too Jon Skeet [C# MVP] - 16 Aug 2004 10:19 GMT > > > Why is it more specific? They both inherit from valuetype. > > > > It's more applicable because there is a widening conversion from the > > enum to Integer, but no widening conversion from Integer to enum. > > what do you mean with widening conversion? I mean exactly what the VB.NET specification says. (See section 8.8 of the spec in MSDN.)
> int and enum are the same size. Irrelevant.
> But now the question arises what if you have enum Test: long{A,B,C} ? There's still a widening conversion from 0 to the enum. There is also a widening conversion from the enum to long, as stated in the language specification as one of the list of widening conversions:
<quote> Conversions from any enumerated type to its underlying type, or to any type that its underlying type has a widening conversion to. </quote>
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
Jay B. Harlow [MVP - Outlook] - 16 Aug 2004 14:36 GMT Code, A widening conversion is one where there is no loss of data. Integer allows all values from Int32.MinValue to In32.MaxValue, while Enum only allows (not enforced) the values that are defined on it (a sub set of Integer).
A narrowing conversion is one where there may be a loss of data.
A widening conversion is an implicit cast operation, while a narrowing conversion is an explicit cast operation.
As Jon pointed out literal 0 is special in that an implicit cast is allowed, while the literal 2 an explicit cast is required. Mostly because we know that 0 is valid for the Enum (as it is its "default" value) while 2 may or may not have been defined in the Enum.
Hope this helps Jay
> > > Why is it more specific? They both inherit from valuetype. > > [quoted text clipped - 20 lines] > > http://www.pobox.com/~skeet > > If replying to the group, please do not mail me too Jon Skeet [C# MVP] - 16 Aug 2004 14:47 GMT > A widening conversion is one where there is no loss of data. That's not quite true - at least not by the VB.NET specification. For instance, there is a widening conversion from Integer to Single, despite the fact that not all System.Int32 values are exactly representable as System.Single values.
The VB.NET specification states: <quote> Widening conversions never overflow but may entail a loss of precision. </quote>
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
Jay B. Harlow [MVP - Outlook] - 16 Aug 2004 15:46 GMT Jon, That's true, I simplified my statement.
Jay
> > A widening conversion is one where there is no loss of data. > [quoted text clipped - 7 lines] > Widening conversions never overflow but may entail a loss of precision. > </quote> Plausible Indirection - 17 Aug 2004 18:12 GMT I've been busy for a few days and I came back to a lot more discussion on this than I expected at first. Thanks to all!
Ok, there seems to be some sloppiness in the language spec. Basically, it does not enforce the restriction of binding of a value to an enumeration type to be one of the values defined for that type. Because of this lack of enforcement, it creates an implicit cast from 0 to enum that would otherwise not always be possible. It is quite possible to define an enumeration that does not contain a valid value for 0.
On top of that, I tried
dt = New Foo(CInt(0), Bar.Bar0)
I would think that the conversion function would remove any possibility that the 0 could be interpreted as an enum, but the behavior remains the same. This just doesn't make sense to me.
I also tried
Enum Bar Bar0 = -1 Bar1 = -2 Bar2 = -4 End Enum
and again there was no behavior change. This really seems like a weakness in the language because, it would be a whole lot simpler, and IMHO, better, to define one rule for interpreting numeric literals as a type rather than multiple rules based on what the value happens to be. Ok, I'd allow promotion along the integer types, but still, in this case, the literal 0 is no more a valid value for the enumeration than the literal 2.
I guess you could argue that the language definition itself _makes_ 0 a more valid value than 2, but that is just a hair-puller for me.
-Chris
> Code, > A widening conversion is one where there is no loss of data. Integer allows [quoted text clipped - 13 lines] > Hope this helps > Jay Jay B. Harlow [MVP - Outlook] - 18 Aug 2004 14:45 GMT Chris,
> I guess you could argue that the language definition itself _makes_ 0 > a more valid value than 2, but that is just a hair-puller for me. Unfortunately with VS.NET 2002 & VS.NET 2003 you will be losing some hair.
:-| As that is how it is currently defined to work! I stated I have not tried VS.NET 2005 (aka Whidbey, due out in 2005) to see if things are improved or not. I really hope they will be!
> On top of that, I tried > > dt = New Foo(CInt(0), Bar.Bar0) Try
Dim zero As Integer = 0
dt = New Foo(zero, Bar.Bar0)
As that is the only way I know of to get VS.NET 2002 & VS.NET 2003 to work. Yes MS knows there is a problem! As I told them a year ago when I came across this quirk and remembered them when you asked your original question.
> Enum Bar > Bar0 = -1 > Bar1 = -2 > Bar2 = -4 > End Enum Remember that
Dim myBar As Bar
Will have the value of literal 0 per the CTS. Also remember that the CLR does not validate values within an Enum, if you need validation of the values you can use Enum.IsDefined.
If Not [Enum].IsDefined(GetType(Bar), myBar) Then Throw New ArgumentOutOfRangeException("myBar", myBar, "Invalid Enum Bar value!") End If
Hope this helps Jay
> I've been busy for a few days and I came back to a lot more discussion > on this than I expected at first. Thanks to all! [quoted text clipped - 53 lines] > > Hope this helps > > Jay
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|