3

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.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Mario Š.
  • 31
  • 3
  • 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 –  Sep 16 '22 at 18:06
  • @MySkullCaveIsADarkPlace I've already read that document several times, but nowhere in there is there an explanation of the ``Field = { { ... }, { ... } }`` type of initialization, nor does it answer how we're able to initialize a getter function. – Mario Š. Sep 16 '22 at 18:09
  • You mean, you are confused by the `{ HttpRequestHeader.Authorization.ToString(), Token }` and `{ HttpRequestHeader.ContentType.ToString(), "multipart/form-data" }` part, and not particularly about collection initializer syntax being used on read-only properties? (If so, please edit your question to make this clear...) –  Sep 16 '22 at 18:10
  • Oh yeah, you are right. I did scour it several times but was looking for a ``{ { ... }, { ... } }`` pattern, which isn't used since they're using a list of objects (``new Cat(...)``) instead. My bad. Thank you! – Mario Š. Sep 16 '22 at 18:12

2 Answers2

0

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

howardButcher
  • 331
  • 1
  • 9
  • 1
    In fact, all that's really needed to make the syntax in the question work is that the respective type implements the non-generic IEnumerable interface (the generic IEnumerable is not a requirement) and provides a public Add(string,string) method -- which the HttpRequestHeaders type of course does. So, no, there are no KeyValuePairs created and added by this collection initialization syntax. –  Sep 16 '22 at 18:29
  • Never said the initialization syntax creates or adds a keyvaluepair. Maybe my answer was a little misleading. Under the hood a keyvaluepair> will be created by your Add(string, string) method. – howardButcher Sep 16 '22 at 18:37
0

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.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75