What does assignment to a bracketed expression mean in C#?
Asked Answered
L

2

30

I'm reading Avalonia source code and I came across this sentence:

return new MenuFlyoutPresenter
{
    [!ItemsControl.ItemsProperty] = this[!ItemsProperty],
    [!ItemsControl.ItemTemplateProperty] = this[!ItemTemplateProperty]
};

I've never seen a syntax like that. What does those bracket do if there is no indexed property or this[] accessor?, and why are they negated with the exclamation mark if the property they are referring to is not a bool?, maybe some kind of null-check?

The code itself is contained in the following cs file:

https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Controls/Flyouts/MenuFlyout.cs

I've tracked the code but I was unable to understand what that syntax does.

Lustihood answered 28/1, 2022 at 13:25 Comment(4)
It's a collection initializerComitia
But collection initializers have curly braces don't they?, also, why are they negated?Lustihood
No they don't, please read the section I linked toComitia
@AleksanderStukov: Collection initializers come in two broad categories: for lists and for dictionaries. This is the syntax used with dictionaries. Also, the UED shouldn't have messed with Raynor.Uncharitable
C
35

There are a couple of things going on here.

First, the syntax:

var menu = new MenuFlyoutPresenter
{
    [key] = value,
};

Is a collection initializer, and is shorthand for:

var menu = new MenuFlyoutPresenter();
menu[key] = value;

That indexer is defined here as:

public IBinding this[IndexerDescriptor binding]
{
    get { return new IndexerBinding(this, binding.Property!, binding.Mode); }
    set { this.Bind(binding.Property!, value); }
}

So the key there is an IndexerDescriptor, and the value is an IBinding.

So, what's going on with this thing?

!ItemsControl.ItemsProperty

We can see from your link that ItemsProperty is a DirectProperty<TOwner, TValue>, and that ultimately implements the ! operator here:

public static IndexerDescriptor operator !(AvaloniaProperty property)
{
    return new IndexerDescriptor
    {
        Priority = BindingPriority.LocalValue,
        Property = property,
    };
}

Avalonia seems to like overloading operators such as ! and ~ to do things you might not expect (and would normally use a method for). In this case, they use ! on an AvaloniaProperty as a shorthand for accessing that property's binding.

Comitia answered 28/1, 2022 at 13:37 Comment(3)
As far as I understand the collection initializer will be transformed into series of .Add() calls. The indexer is a property, so the the first must be a an object initializer, not collection initializer.Legumin
@Legumin See my first link please. The syntax new Foo() { { .., .. } }, would be transformed into a series of Add calls, but the syntax new Foo() { [..] = .. } is transformed into a series of calls to the indexer. The documentation page I linked to documents this under "Collection initializers"Comitia
Although, it's true that the spec document this under object initializers. And the type doesn't need to implement IEnumerable, as it does for collection initializers. EhComitia
H
11

A relatively simple class that demonstrates a way of allowing this syntax is:

public sealed class Demo
{
    public Demo this[Demo index] // Indexer
    {
        get => !index;
        set {} // Not needed to demonstrate syntax. 
    }

    public static Demo operator !(Demo item) => item;

    public Demo ItemsProperty        => _empty;
    public Demo ItemTemplateProperty => _empty;

    public Demo SomeMethod(Demo ItemsControl)
    {
        return new Demo
        {
            [!ItemsControl.ItemsProperty] = this[!ItemsProperty],
            [!ItemsControl.ItemTemplateProperty] = this[!ItemTemplateProperty],
        };
    }

    static Demo _empty = new();
}

Some things to note:

  • Demo implements operator! which allows the ! operator to be used on values of that type (e.g. the !ItemsProperty in the SomeMethod() initialisation).
  • Demo implements an indexer which is what allows the use of the indexing (via this) on the right-hand side of the collection initialiser.
  • The indexer also enables the use of the [x] = y collection initialisation syntax used in SomeMethod().

It's the combination of the operator()! operator along with the this[Demo] indexer that enables the syntax.

Hardnosed answered 28/1, 2022 at 13:41 Comment(2)
that ! operator use is totall not instinctive. (I know not your idea). '!' to me means 'not' so !Foo means 'not foo'. Yet here it means foo. Why is it needed at all?Wraparound
@Wraparound The code in my answer is just an example to show how the syntax works - obviously in real code operator!() should be implemented to actually do something. However, I personally think the Avalonia code is abusing these operators and making the code very unintuitive and hard to read.Hardnosed

© 2022 - 2024 — McMap. All rights reserved.