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.
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...
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...
Typo in the C++/CX block: you have `Iiterable` when you want `IIterable`:
There seems to be a parenthesis/curly brackets mismatch in the code.