"new" on value types

J

Jamie Julius

Consider the following struct:

struct TestStruct
{
public int a, b, c;

public TestStruct(int a, int b, int c)
{
this.a = a;

if (b == 6)
{
throw new ArgumentException("I don't like 6", "b");
}

this.b = b;
this.c = c;
}
}

The C# spec says in section 7.5.10.1:

The run-time processing of an object-creation-expression of the form new
T(A) ... If T is a struct-type:
An instance of type T is created by allocating a temporary local variable.

From this I would assume that if I write the following:

TestStruct ts = new TestStruct();

ts = new TestStruct(1, 2, 3);
ts = new TestStruct(3, 6, 9);

The compiler should invoke the constructor on temporary instances and
subsequently copy the temporaries into "ts". This would imply that if the
constructor throws an exception, "ts" will remain with its original value.

However, the C# compiler seems to pass "ts" itself into the constructor as
the "this" instance (i.e., not a temporary):

IL_0008: ldloca.s ts
IL_000a: ldc.i4.1
IL_000b: ldc.i4.2
IL_000c: ldc.i4.3
IL_000d: call instance void Testing.Item03/TestStruct::.ctor(int32, int32,
int32)

Indeed, if the constructor throws an exception after changing some of the
fields, "ts" results in an inconsistent state.

Basically, I'm curious as to anyone's opinion as to whether this is a
compiler bug, or is the assignment of the value produced by the 'new'
operator a special case.

For comparison, if I change the above to the following:

ts = newTestStruct(1, 2, 3);

where the following is defined:

static TestStruct newTestStruct(int a, int b, int c)
{
return new TestStruct(a, b, c);
}

I get the consistent behavior I was hoping for.

Thanks,

Jamie
 
W

Wiktor Zychla

The C# spec says in section 7.5.10.1:
The run-time processing of an object-creation-expression of the form new
T(A) ... If T is a struct-type:
An instance of type T is created by allocating a temporary local variable.

From this I would assume that if I write the following:

TestStruct ts = new TestStruct();

ts = new TestStruct(1, 2, 3);
ts = new TestStruct(3, 6, 9);

The compiler should invoke the constructor on temporary instances and
subsequently copy the temporaries into "ts". This would imply that if the
constructor throws an exception, "ts" will remain with its original value.

However, the C# compiler seems to pass "ts" itself into the constructor as
the "this" instance (i.e., not a temporary):

IL_0008: ldloca.s ts
IL_000a: ldc.i4.1
IL_000b: ldc.i4.2
IL_000c: ldc.i4.3
IL_000d: call instance void Testing.Item03/TestStruct::.ctor(int32, int32,
int32)

Indeed, if the constructor throws an exception after changing some of the
fields, "ts" results in an inconsistent state.

Basically, I'm curious as to anyone's opinion as to whether this is a
compiler bug, or is the assignment of the value produced by the 'new'
operator a special case.

Jamie,

I've tested your code and can confirm that C# compiler does not use a
temporary variable in value type initialization. It seems that reusing value
types is rather risky then, especially that the docs says that temp variable
IS used.

What is more interesting is that VB.NET compiler does use a temporary
variable for initialization!
In following example (where Class1 is a struct):

Dim c As Class1

c = New Class1

c = New Class1(1, 2, 3)



you get:

..method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01
00 00 00 )
// Code size 26 (0x1a)
.maxstack 4
.locals init ([0] valuetype ConsoleApplication1.Class1 c,
[1] valuetype ConsoleApplication1.Class1 _Vb_t_record_0)
IL_0000: nop
IL_0001: ldloca.s _Vb_t_record_0
IL_0003: initobj ConsoleApplication1.Class1
IL_0009: ldloc.1
IL_000a: stloc.0
IL_000b: ldloca.s _Vb_t_record_0
IL_000d: ldc.i4.1
IL_000e: ldc.i4.2
IL_000f: ldc.i4.3
IL_0010: call instance void
ConsoleApplication1.Class1::.ctor(int32,
int32,
int32)
IL_0015: nop
IL_0016: ldloc.1
IL_0017: stloc.0
IL_0018: nop
IL_0019: ret
} // end of method Module1::Main



where as you can see the _vb_t_record_0 is the temporary vatiable that is
initialized as copied after each initialization:

....

ldloc.1

stloc.0

....



To me it seems that C# compiler should do exactly the same, while it does
not. I really think that this behavior deserves a fundamental explanation! I
hope someone else will comment this one.



Regards,

Wiktor Zychla
 
J

Jon Skeet [C# MVP]

Jamie Julius said:
Consider the following struct:

Basically, I'm curious as to anyone's opinion as to whether this is a
compiler bug, or is the assignment of the value produced by the 'new'
operator a special case.

It looks like a compiler bug to me - logically the assignment really
shouldn't happen until after the constructor has terminated normally.

Have you reported it to MS? Also, have you checked the behaviour on the
beta of VS 2005?
 
J

Jamie Julius

Thanks, Jon and Wiktor for your support on this!
Have you reported it to MS?

No, I haven't. What's the best way to report bugs like this?
Also, have you checked the behaviour on the beta of VS 2005?

No, I haven't done that either. I currently don't have the beta installed.
I'll ask a friend.

Meanwhile, if anyone out there would like to check this on VS 2005, that
would be interesting...

I know that C++ gives some latitude to compilers to optimize away
temporaries. Do you know if similar guidelines exist for C# compilers? I
couldn't find anything in the spec about this. Anyway, I built the test
program with optimizations turned off, so it's unlikely that that's what's
going on.
 
J

Jon Skeet [C# MVP]

Jamie Julius said:
Thanks, Jon and Wiktor for your support on this!


No, I haven't. What's the best way to report bugs like this?

If you're able to check with the beta of VS 2005, use
http://lab.msdn.microsoft.com/productfeedback/default.aspx

Here's a short but complete program which they might find useful (just
your code with the appropriate Main etc):

using System;

struct TestStruct
{
public int a, b, c;

public TestStruct(int a, int b, int c)
{
this.a = a;

if (b == 6)
{
throw new ArgumentException("I don't like 6", "b");
}

this.b = b;
this.c = c;
}

static void Main()
{
TestStruct ts = new TestStruct (1, 2, 3);
try
{
ts = new TestStruct (3, 6, 9);
}
catch
{
// Yes, I know...
}

Console.WriteLine ("{0} {1} {2}", ts.a, ts.b, ts.c);
}
}
No, I haven't done that either. I currently don't have the beta installed.
I'll ask a friend.

Meanwhile, if anyone out there would like to check this on VS 2005, that
would be interesting...

I know that C++ gives some latitude to compilers to optimize away
temporaries. Do you know if similar guidelines exist for C# compilers? I
couldn't find anything in the spec about this. Anyway, I built the test
program with optimizations turned off, so it's unlikely that that's what's
going on.

C# is usually much stricter about what the compiler can and can't do,
fortunately. I'm 99% sure this is a real bug.
 
C

cyost

Here is the VS2005 generated IL. Don't quite understand if this fixes
the problem, but thought someone could look at it...

..method private hidebysig static void Main() cil managed
{
.custom instance void
[mscorlib]System.STAThreadAttribute::.ctor()
.entrypoint
.maxstack 4
.locals (
GenericsPerf.TestStruct struct1)
L_0000: ldloca.s struct1
L_0002: initobj GenericsPerf.TestStruct
L_0008: ldloca.s struct1
L_000a: ldc.i4.1
L_000b: ldc.i4.2
L_000c: ldc.i4.3
L_000d: call instance void GenericsPerf.TestStruct::.ctor(int32,
int32, int32)
L_0012: nop
L_0013: ldloca.s struct1
L_0015: ldc.i4.3
L_0016: ldc.i4.6
L_0017: ldc.i4.s 9
L_0019: call instance void GenericsPerf.TestStruct::.ctor(int32,
int32, int32)
L_001e: nop
L_001f: call void
[System.Windows.Forms]System.Windows.Forms.Application::EnableVisualStyles()
L_0024: nop
L_0025: ret
}
 
W

Willy Denoyette [MVP]

Looks like a v1.x issue only.
Works as documented using v2.0 Beta (VS2005 CTP December drop).

Willy.

Jamie Julius said:
Thanks, Jon and Wiktor for your support on this!
Have you reported it to MS?

No, I haven't. What's the best way to report bugs like this?
Also, have you checked the behaviour on the beta of VS 2005?

No, I haven't done that either. I currently don't have the beta installed.
I'll ask a friend.

Meanwhile, if anyone out there would like to check this on VS 2005, that
would be interesting...

I know that C++ gives some latitude to compilers to optimize away
temporaries. Do you know if similar guidelines exist for C# compilers? I
couldn't find anything in the spec about this. Anyway, I built the test
program with optimizations turned off, so it's unlikely that that's what's
going on.
 
J

Jamie Julius

Thanks, cyost.

In this IL it looks like the bug still exists.

However, Willy in the post following yours claims that in his version (v2.0
Beta - VS2005 CTP December drop) it has been fixed. Which version did you
test with?

Here is the VS2005 generated IL. Don't quite understand if this fixes
the problem, but thought someone could look at it...

.method private hidebysig static void Main() cil managed
{
.custom instance void
[mscorlib]System.STAThreadAttribute::.ctor()
.entrypoint
.maxstack 4
.locals (
GenericsPerf.TestStruct struct1)
L_0000: ldloca.s struct1
L_0002: initobj GenericsPerf.TestStruct
L_0008: ldloca.s struct1
L_000a: ldc.i4.1
L_000b: ldc.i4.2
L_000c: ldc.i4.3
L_000d: call instance void GenericsPerf.TestStruct::.ctor(int32,
int32, int32)
L_0012: nop
L_0013: ldloca.s struct1
L_0015: ldc.i4.3
L_0016: ldc.i4.6
L_0017: ldc.i4.s 9
L_0019: call instance void GenericsPerf.TestStruct::.ctor(int32,
int32, int32)
L_001e: nop
L_001f: call void
[System.Windows.Forms]System.Windows.Forms.Application::EnableVisualStyles()
L_0024: nop
L_0025: ret
}



Jamie said:
Thanks, Jon and Wiktor for your support on this!


No, I haven't. What's the best way to report bugs like this?


No, I haven't done that either. I currently don't have the beta installed.
I'll ask a friend.

Meanwhile, if anyone out there would like to check this on VS 2005, that
would be interesting...

I know that C++ gives some latitude to compilers to optimize away
temporaries. Do you know if similar guidelines exist for C# compilers? I
couldn't find anything in the spec about this. Anyway, I built the test
program with optimizations turned off, so it's unlikely that that's what's
going on.
 

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