Unintuitive behaviour with struct initialization and default arguments

后端 未结 5 2300
小鲜肉
小鲜肉 2021-02-12 17:52
public struct Test 
{
    public double Val;
    public Test(double val = double.NaN) { Val = val; }
    public bool IsValid { get { return !double.IsNaN(Val); } }
}

Te         


        
5条回答
  •  [愿得一人]
    2021-02-12 18:46

    I find this difference in behaviour and particularly the behaviour in the struct case suprising and unintuitive - what is going on? What does the default arg on the stuct construct serve? If its useless why let this compile?

    It serves nothing. The emitted IL code wont generate a call to the constructor with the default parameter, but will call default(Test). It seems totally reasonable that the compiler would emit a warning saying the constructor will not be invoked (although that is an implementation detail). I file an issue on http://connect.microsoft.com

    If we look at the generated IL code for:

    Test myTest = new Test();
    bool valid = myTest.IsValid;
    

    We'll see:

    IL_0000:  ldloca.s    00 // myTest
    IL_0002:  initobj     UserQuery.Test // default(Test);
    IL_0008:  ldloca.s    00 // myTest
    IL_000A:  call        UserQuery+Test.get_IsValid
    

    Note the call made in IL isn't a method call to the constructor (which would look like: call Test..ctor) , it generated a call to initobj:

    Initializes each field of the value type at a specified address to a null reference or a 0 of the appropriate primitive type. Unlike Newobj, initobj does not call the constructor method. Initobj is intended for initializing value types, while newobj is used to allocate and initialize objects.

    Which means the compiler is merely disregarding the constructor with default parameters, as until C#-6.0 it is forbidden to declare such a constructor.

    @JonSkeet takes this to great depth in his answer to Does using "new" on a struct allocate it on the heap or stack?

    Edit:

    I actually asked Mads Torgerson a question regarding the new use of the parameterless constructor in C#-6.0 which i think is related, and he said:

    @Yuval and others, regarding parameterless constructors on structs: the thing to realize is that, before and now, constructors don't necessarily run on structs. All we did was add the ability to have an parameterless constructor that also cannot be guaranteed to run. There is no reasonable way to have structs that are guaranteed to have been initialized, and parameterless constructors don't help with that.

    The thing parameterless constructors help with is allowing you to have a parameterless constructor.

    I think a main source of confusion is that 'new S()' is allowed to mean 'default(S)'. That is a historical mistake in the language and I dearly wish I could take it away. I would strongly discourage anyone from using 'new S()' on a struct that doesn't have a parameterless constructor. As far as I can tell, this is in because the default(S) syntax didn't exist in C# 1.0, so this was just the syntax used for getting a default value of a struct.

    It is true that the compiler implementation has so far "optimized" 'new T()' to mean essentially default(T) when T is a struct. That was actually a bug - it was always supposed to call an actual parameterless constructor if there is one - which there could have been all along, since it is allowed in IL. We are fixing this, so that we will call the constructor even in the generic case.

    The semantics therefore is clean: new S() is the only way to run a parameterless constructor on a struct, and it always runs that constructor - even through generics.

提交回复
热议问题