dynamic
interact with pattern matching?Is there an issue using array/collection initializer syntax for list patterns that would apply to anything IList<T>
, including arrays?
int[] array = new [] { 1, 2, 3 };
var list = new List<int>() { 1, 2, 3 };
let { 1, var second, 2 } = array else return;
let { 1, 2, var third } = list else return;
A sequence wildcard pattern would be nice to indicate that you don't care how many previous or subsequent elements there might be:
var list = GetListOfNumbers();
let { var first, **, var last } = list else return;
F# has no such facility.
The cons pattern is interesting, but I'm not aware of a CLR type that it would map to cleanly. IIRC in functional languages it always is used to construct/deconstruct an immutable linked list. IEnumerable<T>
is academically interesting but probably impractical.
As for dynamic, I don't think that the current implementation provides a way to inspect truly dynamic types? Otherwise, I'd try to support the gamut of patterns through emitting code that inspects the dynamic type for the appropriate shape.
dynamic expando = new ExpandoObject();
expando.Foo = 123;
expando.Bar = "456";
switch (expando) {
case { Foo is 123, Bar is "456" }:
Console.WriteLine("matched!");
break;
}
As I have said in one of the other issues, I feel that arrays/lists will be well-served by a cons pattern coupled with list views/slices/segments. Enumerables are better off with LINQ. Dynamics can be matched against a property pattern. I have nothing to offer wrt dictionaries.
I think complete analogy with _object-or-collection-initializer_ would be really neat and I don't think it would be ambiguous at all (that's why we have a _non-assignment-expression_ in collection initializers).
And for dictionaries we can use indexer patterns (#10600 (comment)).
Is there an issue using array/collection initializer syntax for list patterns that would apply to anything IList
, including arrays?
If possible, please make that some sort of duck-typed IReadOnlyList<T>
instead - IEnumerable<T>
with an indexer with a getter of T
and a long
or int
Count
. (In the tradition of awaitables, foreach and collection initializers being speced using duck typing instead of interfaces. If IReadOnlyList<T>
had been there right in 2.0, everyone would have adopted it and this wouldn't have been necessary.)
A sequence wildcard pattern would be nice to indicate that you don't care how many previous or subsequent elements there might be:
I propose using var first, ..., var last
instead, which conveys more "multi-elemental-ness" (it is literally the yada-yada-yada operator in Perl) than just repeating the *
. Or why not var first, params, var last
? (Just kidding. 馃槣)
For dictionaries, how about:
var d = new Dictionary<string, int> {
["trite"] = 42
};
switch (d) {
case var answer = ["trite"]:
// ...
}
var elaborate = new Dictionary<string, string {
["type"] = "string",
["integer"] = null,
["string"] = "xyz"
};
switch (elaborate) {
// I am unsure about whether the following syntax is enabled by recursive patterns
case var value = [var type = ["type"]]:
// ...
}
To avoid exceptions when getting things that are simply not there, this would use, in order of availability:
TryGetValue(TKey, out TValue)
(implementable without being a dictionary, again like Add
in collection initializers, would be picked up in IReadOnlyDictionary<TKey, TValue>
and Dictionary<TKey, TValue>
)Contains(TKey)
followed by this[TKey] => TValue
; not atomic and could throw during races, but not more or less dangerous than just writing that code outside of a pattern. Limited to dictionaries (like non-generic IDictionary
and Hashtable) that don't implement TryGetValue
.this[key]
, but throw if the indexer didThe above pattern could also be adapted to random access list/collection index matching.
Pattern matching with arrays & lists will be a bit difficult since C# (still) doesn't have array & list literals!
Normally pattern matching is understandable because the syntax to deconstruct and to construct are the same. Consider tuples: var (x, y) = (1, 2);
. There is a pleasing, easy-to-understand symmetry to this form, thanks to the tuple literal. How would it work with arrays?
let (new [] { var first }) = new [] {1, 2, 3} else return;
This has symmetry but looks really awkward... Any chance of C# finally having real, honest to god list and array literals? I'm going to completely rip off F# here with a suggestion:
let myList = [1, 2, 3];
switch (myList)
{
case [var first, **] : return first;
case [] : throw new InvalidOperationException("Cannot get first item of empty list");
}
And for arrays:
let myArray = [| 'a', 'b', 'c' |];
switch (myArray)
{
case [| var first, ** |] : return first;
case [| |] : throw new InvalidOperationException("Cannot get first item of empty array");
}
While the cons
idea seems right at first glance, I don't think it's right for C# since List
IEnumerable is not appropriate for deconstructing because, as hinted at above, you're quite possibly causing side effects or causing re-evaluation of some sequence by walking the IEnumerable.
Actually, a better idea might be to support some duck typing here.
The case [var first, var second]
pattern can be used to deconstruct _any type_ that supports an int-indexer, i.e. implements this[int index]
. Perhaps the type should also implement Count
since case [var first, var second]
technically will only match a list of length 2;
Issue moved to dotnet/csharplang #898 via ZenHub
Most helpful comment
Is there an issue using array/collection initializer syntax for list patterns that would apply to anything
IList<T>
, including arrays?A sequence wildcard pattern would be nice to indicate that you don't care how many previous or subsequent elements there might be:
F# has no such facility.
The cons pattern is interesting, but I'm not aware of a CLR type that it would map to cleanly. IIRC in functional languages it always is used to construct/deconstruct an immutable linked list.
IEnumerable<T>
is academically interesting but probably impractical.As for dynamic, I don't think that the current implementation provides a way to inspect truly dynamic types? Otherwise, I'd try to support the gamut of patterns through emitting code that inspects the dynamic type for the appropriate shape.