May 30th, 2022

On passing iterables of iterables in the Windows Runtime

The Windows Runtime API design guidelines recommend that methods accept iterables rather than specific collections where possible. This allows clients to pass any iterable collection of their choosing, or possibly no collection at all (like a LINQ query).

The generalization rules are:

  • Arrays of T are accepted as IIterable<T>.
  • Maps from K to V are accepted as IIterable<IKeyValuePair<K, V>>.

This works out well in practice because iterable collections support IIterable, so you can just pass the collection, and the language projection will pass the IIterable to the method.

Things get a little weirder if you want to pass a collection of collections. For example, suppose you have a method which wants to accept an array of maps.

void Something(IVector<IMap<String, String>> attributesArray);

Applying the above guidance means that your method looks like this:

void Something(IIterable<IIterable<IKeyValuePair<String, String>>> attributesArray);

C# supports covariance, but C++ does not. This means that in C#, you can say

    var v = new[] {
            new Dictionary<string, string> { ["foo"] = fooValue },
            new Dictionary<string, string> { ["bar"] = barValue },
    };
    var result = Something(v);

and the language lets you pass an IEnumerable<Dictionary<string, string>> instead of an IEnumerable<IEnumerable<IKeyValuePair<string, string>>>.

C++ does not support covariance. If you try the equivalent C++, it fails.

// C++/CX
    using AttributeMap = Map<String^, String^>;
    AttributeMap^ v[2] = {
       ref new AttributeMap{ { "foo", fooValue } },
       ref new AttributeMap( { "bar", barValue } },
    };
    auto result = Something(v); // does not compile

// C++/WinRT
    using AttributeMap = IMap<hstring, hstring>;
    AttributeMap v[2] = {
        single_threaded_map(std::map<hstring, hstring>{ { L"foo", fooValue } },
        single_threaded_map(std::map<hstring, hstring>{ { L"bar", barValue } },
        };
    auto result = Something(v); // does not compile

You have to misdeclare the inner maps as iterables.

// C++/CX
    using AttributeMap = Map<String^, String^>;
    using IAttributeIterable = Iiterable<IKeyValuePair<String^, String^>>;
    auto d1 = ref new AttributeMap{ { "foo", fooValue } };
    auto d2 = ref new AttributeMap( { "bar", barValue } };
    auto v = ref new Vector<IAttributeIterable>({ d1, d2 });
    auto result = Something(v);

// C++/WinRT
    using IAttributeIterable = IIterable<IKeyValuePair<hstring, hstring>>;
    auto v = std::vector<IAttributeIterable>({
        std::map<hstring, hstring>({ { L"foo", fooValue } }),
        std::map<hstring, hstring>({ { L"bar", barValue } }),
    });
    auto result = Something(v);

Fortunately, C++/WinRT gives you a bit of help. The winrt::params namespace lets you write

    auto d1 = std::map<hstring, hstring>({ { L"foo", fooValue } });
    auto d2 = std::map<hstring, hstring>({ { L"bar", barValue } });
    auto result = Something({ d1, d2 });

And the appropriate conversions will happen. So C++/WinRT is roughly on par with C#. C++/CX is kind of awkward though. Sorry, C++/CX.

Bonus chatter: After writing this up, I discovered that I had already written about it, in a different way. So now you have two ways of reading about the same topic.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

4 comments

Discussion is closed. Login to edit/delete existing comments.

  • Sukru Tikves

    The rule might make sense for lists as iterables, but for maps, I would have preferred to keep them as just generic IMaps

    The reason is, by definition, Lists are a collection of items, with no specific order. (Of course we can sort them, but that is a different story).

    On the other hand Maps are used for efficient lookups, possibly in O(logN) or O(1) time (tree or hashtable). Converting that to a generic iterable of KVPs...

    Read more
    • Raymond ChenMicrosoft employee Author · Edited

      In practice, Windows Runtime methods usually read all the keys and values. For example, you pass a map of things to convert to JSON. The JSON converter has to process the entire contents of the map. Or it's a map of option names and values. The method has to process the entire contents of the map to make sure it honored every option. The lookup efficiency is immaterial because the consumer never performs a lookup.

      By...

      Read more
  • Jonathan PryorMicrosoft employee

    Typo in the C++/CX block: you have `Iiterable` when you want `IIterable`:

    using IAttributeIterable = IIterable<IKeyValuePair>;
  • Hans van Leuken - Melis

    There seems to be a parenthesis/curly brackets mismatch in the code.

    auto d1 = ref new AttributeMap{ { "foo", fooValue } };
    auto d2 = ref new AttributeMap( { "bar", barValue } };