Object initialization syntax on get-only field [HttpsRequestMessage.Header]

528 Views Asked by At

While searching around for how to work with HTTP request headers, i've seen a ton of examples use this construct to initialize the HttpRequestMessage:

var request = new HttpRequestMessage()
{
    Headers =
    {
        { HttpRequestHeader.Authorization.ToString(), Token },
        { HttpRequestHeader.ContentType.ToString(), "multipart/form-data" }
    },
    Method = HttpMethod.Post,
    RequestUri = new Uri(endpointUrl),
    Content = content
};

This seems to work fine, and the compiler isn't complaining, not even registering a warning, but i'm very confused about the Headers field initialization.

The Headers field in the source code is defined as:

public HttpRequestHeaders Headers
{
    get
    {
        if (headers == null)
        {
            headers = new HttpRequestHeaders();
        }
        return headers;
    }
}

I'm wondering, how is it possible to initialize a field that only has a get function?
Even if it's somehow initializing the underlying private HttpRequestHeaders headers (even though i'm pretty sure it doesn't work like that), i've never seen the Field = { { ... }, { ... } } type of initialization in C#.

It's reminiscent of the Dictionary initializer, but it's missing the new HttpRequestHeaders for that to be the case.

This is the first and only time i've seen this type of initialization and cannot find any reference to it in the docs or on SO.

2

There are 2 best solutions below

2
howardButcher On

HttpRequestHeader implements System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string,System.Collections.Generic.IEnumerable<string>>>

That means you can add elements to this, in this case KeyValuePair-Elements. You are not initializing the Headers-Property, you add KeyValuePairs to it.

See https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#object-initializers-with-collection-read-only-property-initialization

0
Peter Csala On

How is it possible to initialize a field that only has a get function?

If a property does not a have a setter that does not mean that you can't initialize it.
It means you can't change the value after the object has been initialized.

So, the following code would generate a compile-time error:

var request = new HttpRequestMessage()
{
    Method = HttpMethod.Post,
    RequestUri = new Uri(endpointUrl),
    Content = content
};
//This is NOT allowed
request.Headers =
{
    { HttpRequestHeader.Authorization.ToString(), Token },
    { HttpRequestHeader.ContentType.ToString(), "multipart/form-data" }
},

The getter only properties are working in the same way as the readonly fields.
They can be initialized at their declaration

public class Test
{
   public readonly int X = 1;
   public int Y { get; } = 1;
}

or inside any constructor

public class Test
{
    public readonly int X;
    public int Y { get; }
    
    public Test()
    {
        X = 1;
        Y = 1;
    }
    
    public Test(int x, int y)
        => (X, Y) = (x, y);
}

You can use the object initializer for readonly field / getter-only property if their data type are not value type rather reference type (class/record).

public class Test
{
    public readonly TestData X;
    public TestData Y { get; }
}

public class TestData
{
    public int Z { get; set; }
}
var t1 = new Test { Y = { Z = 1 }, X = { Z = 1 } };
//OR
Test t2 = new () { Y = { Z = 1 }, X = { Z = 1 } };

If the TestData would be a struct or record struct then the code would not compile.


If you want to have a property which value can't be set after initialization and its data type is value type then you have to use init:

public class Test
{
    public int Y { get; init; };
}
var t = new Test { Y = 1 };
//This is NOT allowed
t.Y = 2;

So, you can set the HttpRequestMessage's Header property inside the object initializer because the Header's data type is HttpRequestHeaders, which is a reference type.