Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

out parameters of struct type not required to be assigned

Tags:

c#

.net

I've noticed some bizarre behavior in my code when accidentally commenting out a line in a function during code review. It was very hard to reproduce but I'll depict a similar example here.

I've got this test class:

public class Test
{
    public void GetOut(out EmailAddress email)
    {
        try
        {
            Foo(email);
        }
        catch
        {
        }
    }

    public void Foo(EmailAddress email)
    {
    }
}

there is No assignment to Email in the GetOut which normally would throw an error:

The out parameter 'email' must be assigned to before control leaves the current method

However if EmailAddress is in a struct in a seperate assembly there is no error created and everything compiles fine.

public struct EmailAddress
{
    #region Constructors

    public EmailAddress(string email)
        : this(email, string.Empty)
    {
    }

    public EmailAddress(string email, string name)
    {
        this.Email = email;
        this.Name = name;
    }

    #endregion

    #region Properties

    public string Email { get; private set; }
    public string Name { get; private set; }

    #endregion
}

Why doesn't the compiler enforce that Email must be assign to? Why does this code compile if the struct is created in a separate assembly, but it doesn't compile if the struct is defined in the existing assembly?

like image 979
johnny 5 Avatar asked Oct 30 '19 18:10

johnny 5


People also ask

What is an out parameter?

The out parameter in C# is used to pass arguments to methods by reference. It differs from the ref keyword in that it does not require parameter variables to be initialized before they are passed to a method. The out keyword must be explicitly declared in the method's definition​ as well as in the calling method.

Can out parameter be optional C#?

No. To make it "optional", in the sense that you don't need to assign a value in the method, you can use ref . A ref parameter is a very different use case.

What is out _ in C#?

The out is a keyword in C# which is used for the passing the arguments to methods as a reference type. It is generally used when a method returns multiple values.

Do you need to declare an out variable before you use it?

Calling a method with an out argument In C# 6 and earlier, you must declare a variable in a separate statement before you pass it as an out argument. The following example declares a variable named number before it is passed to the Int32.


1 Answers

TLDR: This is a known bug of long standing. I first wrote about it in 2010:

https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/

It is harmless and you can safely ignore it, and congratulate yourself on finding a somewhat obscure bug.

Why doesn't the compiler enforce that Email must be definitely assigned?

Oh, it does, in a fashion. It just has a wrong idea of what condition implies that the variable is definitely assigned, as we shall see.

Why does this code compile if the struct is created in a separate assembly, but it doesn't compile if the struct is defined in the existing assembly?

That's the crux of the bug. The bug is a consequence of the intersection of how the C# compiler does definite assignment checking on structs and how the compiler loads metadata from libraries.

Consider this:

struct Foo 
{ 
  public int x; 
  public int y; 
}
// Yes, public fields are bad, but this is just 
// to illustrate the situation.
void M(out Foo f)
{

OK, at this point what do we know? f is an alias for a variable of type Foo, so the storage has already been allocated and is definitely at least in the state that it came out of the storage allocator. If there was a value placed in the variable by the caller, that value is there.

What do we require? We require that f be definitely assigned at any point where control leaves M normally. So you would expect something like:

void M(out Foo f)
{
  f = new Foo();
}

which sets f.x and f.y to their default values. But what about this?

void M(out Foo f)
{
  f = new Foo();
  f.x = 123;
  f.y = 456;
}

That should also be fine. But, and here is the kicker, why do we need to assign the default values only to blow them away a moment later? C#'s definite assignment checker checks to see if every field is assigned! This is legal:

void M(out Foo f)
{
  f.x = 123;
  f.y = 456;
}

And why should that not be legal? It's a value type. f is a variable, and it already contains a valid value of type Foo, so let's just set the fields, and we're done, right?

Right. So what's the bug?

The bug that you have discovered is: as a cost savings, the C# compiler does not load the metadata for private fields of structs that are in referenced libraries. That metadata can be huge, and it would slow down the compiler for very little win to load it all into memory every time.

And now you should be able to deduce the cause of the bug you've found. When the compiler checks to see if the out parameter is definitely assigned, it compares the number of known fields to the number of fields that were definite initialized and in your case it only knows about the zero public fields because the private field metadata was not loaded. The compiler concludes "zero fields required, zero fields initialized, we're good."

Like I said, this bug has been around for more than a decade and people like you occasionally rediscover it and report it. It's harmless, and it is unlikely to be fixed because fixing it is of almost zero benefit but a large performance cost.

And of course the bug does not repro for private fields of structs that are in source code in your project, because obviously the compiler already has information about the private fields at hand.

like image 53
Eric Lippert Avatar answered Oct 19 '22 17:10

Eric Lippert