On passing iterables of iterables in the Windows Runtime

Raymond Chen

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.

4 comments

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

  • Hans van Leuken - Melis 0

    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 } };
    
  • Jonathan PryorMicrosoft employee 0

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

    using IAttributeIterable = IIterable<IKeyValuePair>;
  • Sukru Tikves 0

    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 is significantly inefficient.

    • Raymond ChenMicrosoft employee 0

      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 analogy, when you pass a list of things to the Windows Runtime, it rarely uses the random-access feature of vectors. It just enumerates the entire vector contents. The only thing it really requires is iterability.

Feedback usabilla icon