Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Feedback on Collection Expressions#7666

Unanswered
KathleenDollard asked this question inGeneral
Nov 8, 2023· 15 comments· 83 replies
Discussion options

C# 12 introduces collection expressions:https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#collection-expressions

What else would you like to see and what are the important scenarios to you? Dictionaries? Support forvar (natural types)?

You must be logged in to vote

Replies: 15 comments 83 replies

Comment options

I'd love to be able to use collection expressions to type JSON literals. For example,https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/use-dom has this sample code:

varforecastObject=newJsonObject{["Date"]=newDateTime(2019,8,1),["Temperature"]=25,["Summary"]="Hot",["DatesAvailable"]=newJsonArray(newDateTime(2019,8,1),newDateTime(2019,8,2)),["TemperatureRanges"]=newJsonObject{["Cold"]=newJsonObject{["High"]=20,["Low"]=-10}},["SummaryWords"]=newJsonArray("Cool","Windy","Humid")};

but it would be nice to write it as something like this:

JsonObjectforecastObject=["Date":newDateTime(2019,8,1),"Temperature":25,"Summary":"Hot","DatesAvailable":[newDateTime(2019,8,1),newDateTime(2019,8,2)],"TemperatureRanges":["Cold":["High":20,"Low":-10]],"SummaryWords":["Cool","Windy","Humid"]];
You must be logged in to vote
7 replies
@huoyaoyuan
Comment options

Collection literals are target typed.

Use JSON here as an example. JsonNode isn't directly creatable, but it should expose creation fromboth JsonArray and JsonObject, since it's the element type of them. This can introduce ambiguous for empty collection. Of coursenew JsonArray ornew JsonObject do work here.

@CyrusNajmabadi
Comment options

We might consider a syntactic form to state that the result must be a dictionary-type. But it's low on our list of cares currently.

@TahirAhmadov
Comment options

Isn't this an API request for the maintainer of theJsonObject class (whoever it is)? If the exposedJsonObject.Add method or whatever takesJsonNode as the second parameter, and saidJsonNode has implicit operators from all these primitive types, as well as collection expression surface area of its own, shouldn't the current syntax already support what's requested here?

@GabeSchaffer
Comment options

The important part of this request isn't that theJsonNode supports collection expressions, but that the C# spec for collection expressions makes that support possible.

This capability will be very useful with JSON APIs.

@TahirAhmadov
Comment options

@GabeSchaffer that's the thing - if I'm not mistaken, as designed (proposed) today, C# should support this kind of expression initialization - given the correct public surface area of the types in question.

Comment options

What else would you like to see and what are the important scenarios to you? Dictionaries? Support forvar (natural types)?

Yes and yes to dictionaries andvar.

Whilst this will introduce a third way of initialising a dictionary, it will be super-nice syntax and is definitely worth doing in my view.

Regardingvar, I can see this being a re-run of "even better betterness" continuous improvement process. What I mean by that is that a simple implementation likely will suffice for v1 (maybe just defaults to a struct type if used locally and anIList<T> implementation if passed to other methods/ returned). Over time, the team can then respond to compelling use cases where smarter typing is needed. But there likely is little point in trying to pre-guess what those use cases might be and it likely will evolve over time.

You must be logged in to vote
9 replies
@TahirAhmadov
Comment options

I view forward-looking as valuable because I and probably many others have this very common pattern everywhere:

varlist=newList<BlaBla>();// so tired of typing this outvarlist=[];// bingowhile(...){// ... stuff ...list.Add(newBlaBla{ ...});}// use list for something - return, set property, etc.
@HaloFour
Comment options

IMO the rules would end up being ridiculously complicated and run into the same problems of "best common type" that have basically made this inference impractical anywhere else it's been suggested in the language.

@CyrusNajmabadi
Comment options

Fwiw, I have a proposal for doing this. I think this is space worth looking into. I think we can find a sweet spot that is not too complex, while also supporting the majority of cases

@HaloFour
Comment options

Would that change the conversation around other forms of deferred inference that had been effectively rejected in the past?

@CyrusNajmabadi
Comment options

Very likely yes. My proposal is not specific to collection expressions. That said, we might use collections to try out this space before broadening to other types.

Comment options

I want a way to omit null values from my collection expression.

Ie:

var Values = [?x, ?..y];

You must be logged in to vote
16 replies
@sab39
Comment options

For conditionally including single items in a collection expression, how about a suffix?, as in[a, b?, c]? The usual problem with using? is ambiguity with existing language syntax, but as far as I can figure out that's not an issue here, because this use of? would always have to be followed by, or], neither of which are legal after? in ternaries or any other existing constructs using?.

@CyrusNajmabadi
Comment options

@sab39 Def an option. I'm also a fan of[ a, b, ? c, ? d]. To keep the sigil near the beginning (like with spreads). We'll def consider several options. And i think theadd if not null case is potentially important enough to first-class.

@sab39
Comment options

@CyrusNajmabadi I don't want to get too far into bikeshedding a feature that's speculative in the first place (and worth doing no matter what syntax is selected, imo) but I'd like to give my rationale for suggesting? as a suffix rather than a prefix. It boils down to existing uses of? in the language, especially the other uses of? to indicate null-conditional (that is,??,?. and?[]). In each of those cases, the operation is conditional on the thing to theleft of the? being non-null. The other places where? is used in the language are the ternary operator (again, conditional on the thing to the left of the?) and when naming nullable value types (where the? acts as a modifier to the thing on the left of it). So there's a pretty strong precedent in the language for? as a modifier conditional on what comes before it. I feel like it'd be jarring if in this one scenario only, you look to the right of the? rather than the left.

@CyrusNajmabadi
Comment options

@sab39 understood. And we'd def consider that. IMO, i'd still prefer? on the left to be in line with... It too acts on the thing on the right. But no decisions have been made on this and we'll put every option out there for consideration. Thanks!

@TonyValenti
Comment options

I prefer it on the left as I feel like it has symmetry with ?.

Comment options

I'd like to see support forISet<T>:

Before:

protectedstaticreadonlyISet<string>SupportedImageFileMimeTypes=newHashSet<string>(){"image/jpeg","image/png","image/webp"};

After:

protectedstaticreadonlyISet<string>SupportedImageFileMimeTypes=["image/jpeg","image/png","image/webp"];
You must be logged in to vote
20 replies
@TahirAhmadov
Comment options

@stephentoub how about a flag on the attribute for "static readonly"? Some types get 2 attributes. Then the language can use one of the two strategies where applicable depending on the site

@CyrusNajmabadi
Comment options

I woudl be fine with unsorted (as nothing about teh core interfaces indicates it is sorted). I think if we wanted sorted-sets to be something to target, perhaps having anISortedSet<T> could make sense. So that the api strongly indicates that you do get a set you expect to be sorted.

@KennethHoff
Comment options

Speaking of sorted collection interfaces; what aboutIOrderedEnumerable - does that support collection expressions? What does/would it default to?

@CyrusNajmabadi
Comment options

The only interfaces the language supports are those that array implements. Any other interfaces need the `[CollectionBuilder] attribute added to them.

@stephentoub
Comment options

what about IOrderedEnumerable - does that support collection expressions?

IOrderedEnumerable is an implementation detail that out of necessity is part of the public API. It's only purpose is to allow ThenBy to be available only after an OrderBy, as otherwise the ThenBy is meaningless. We wouldn't want collection expressions to be able to create an IOrderedEnumerable as doing so would go against its purpose.

@stephentoub how about a flag on the attribute for "static readonly"? Some types get 2 attributes. Then the language can use one of the two strategies where applicable depending on the site

That's adding language and library complexity for something that can more easily be solved just by someone expressing exactly what they want, e.g. typing the field asFrozenSet<T> or adding a cast.

Comment options

Extension method support (separate from natural type) to allow things like:

publicstaticImmutableArray<T>AsImmutableArray<T>(thisImmutableArray<T>items)=>items;// consumed elsewhere asvarmyImmutableArray=[1,2,3].AsImmutableArray();
You must be logged in to vote
0 replies
Comment options

Dictionary<string, int> d = [ [ "key1", 0 ], [ "key2", 1 ] ];

You must be logged in to vote
8 replies
@jnm2
Comment options

jnm2Nov 15, 2023
Collaborator

@sab39 Looks like we had similar thoughts.#7666 (comment)could easily be extended to support tuples.

@jnm2
Comment options

jnm2Nov 15, 2023
Collaborator

If that's true, what are the odds the BCL team would be open to adding that conversion, and if they did, would the special dictionary syntax even be necessary?

I think we might still want the[a: b] syntax. So far, we're planning it to have overwrite semantics (dictionary indexer calls), not throw semantics (Add calls).

@sab39
Comment options

@sab39 Looks like we had similar thoughts.#7666 (comment) could easily be extended to support tuples.

Could it, though? Wouldn't the dictionary type have to implementIEnumerable<ValueTuple<string, int>> for that to be legal?

@jnm2
Comment options

jnm2Nov 15, 2023
Collaborator

It can! Collection initializers have never needed the Add parameter type(s) to match the collection item type. Collection expressions which construct via Add calls are consistent with that.

@jnm2
Comment options

jnm2Nov 16, 2023
Collaborator

Actually, I'm wrong, due to a change in direction that happened very late:https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md#collection-expressions

The implicit conversion is necessary; collection expressions which construct with Add calls donot support all the scenarios that collection initializers do.

Comment options

jnm2
Nov 15, 2023
Collaborator

ℹ️ Until there is actual support for dictionary literals, the following syntax does work (sharplab) for C# 12 so long as you're willing to declare an extension method:

Dictionary<int,string>dict1=[new(1,"one"),new(2,"two")];Dictionary<int,string>dict2=[..dict1,new(3,"three")];publicstaticclassExtensions{publicstaticvoidAdd<TKey,TValue>(thisIDictionary<TKey,TValue>dictionary,KeyValuePair<TKey,TValue>entry){dictionary.Add(entry);}}

(Don't worry, this isn't recursive. It binds tovoid ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item).)

You must be logged in to vote
0 replies
Comment options

Very nice new method to get clean code!

However, there is one thing in automatic formatting.
Expected:
private string[] PropertyList = ["ClassCode", "CycleState", "DeliveryTimeMax", "Gtin", "Successor"];
But there is sometimes unexpected auto-formatting (position of newlines, space and square brackets) and there not yet options to set in the Formatting options for C# as of Visual Studio 17.8.
image

You must be logged in to vote
1 reply
@CyrusNajmabadi
Comment options

@vriesdea Please file an issue at github.com/dotnet/roslyn. Please provide a full repro if you can. Thanks. :)

Comment options

A way to create ILookup would be appreciated (probably by returning an instance of the internal Lookup class). Currently, the only way to create them is using the .ToLookup() extension method.
I assume that it would probably use a syntax close to the one that will be used for dictionaries.

ILookup<int, string> x = [ 1 => ["a", "b", "c"], 2 => ["d"] ];

You must be logged in to vote
0 replies
Comment options

Suggestion 1. Integer range expressions within collection expressions, e.g.:

int[]array1=[0..n];// n elements from 0 inclusive to n exclusivelong[]array2=[0..10,20..30,100];// 21 elements

Here,a..b would be a special syntax in whicha andb are target-typed integers. It wouldnot be an instance of typeSystem.Range, so e.g. the^ operator would not be allowed.

Suggestion 2 (along with suggestion 1). Collection expressions inforeach statements, ideally without the enclosing brackets, e.g.:

foreach(intiin0..n){Something(i);}foreach(longiin0..10,20..30,100){Something(i);}

Of course, this would be a "collection expression" only when there are at least two elements or a range.

You must be logged in to vote
4 replies
@sab39
Comment options

You can get the first part of suggestion 2 quite easily today with an extension method:

publicstaticIEnumerable<int>Each(thisRangerange){if(range.Start.IsFromEnd||range.End.IsFromEnd)thrownewArgumentException("...");returnEnumerable.Range(range.Start.Value,range.End.Value-range.Start.Value);}publicstaticIEnumerator<int>GetEnumerator(thisRangerange)=>range.Each().GetEnumerator();foreach(variin0..n){Something(i);}

Yes, that's technically two extension methods - they could easily be combined of course, butEach() feels useful by itself. For example, it gives a way (albeit a clumsy way) to achieve your suggestion 1 as well:

int[]array1=[..(0..n).Each()];// (0..n).Each().ToArray() would probably be cleaner herelong[]array2=[..(0..10).Each(), ..(20..30).Each(),100];

Long term, the existing proposal for extensions will let you new interfaces to existing types. At that point you can extendRange to directly implementIEnumerable<int>, and getalmost the exact syntax you wanted, just with a few extra dots and parens:

foreach(longiin[..(0..10), ..(20..30),100]){Something(i);}
@DavidArno
Comment options

I'd love to see both of these implemented. There doesn't seem any real interest in the language team in addressing this missing functionality though.

@sab39, the problem with using extension methods to work around this feature gap is thatif (range.Start.IsFromEnd || range.End.IsFromEnd) throw new ArgumentException("..."); line: it's all a bit "hacky" as it fails at runtime, not compile time. Working around that in turn then gets more complex as it needs analyzers that need to be able to check the types used by the start and end values are whole number values, not indexes. It's a achievable, but is far more complex than your initial code.

And as you say yourself, this only achieves a small part of what could be achieved if the C# language directly supported value ranges, rather than the current index-based ranges. As for shapes, extension everything and all those other much promised utopia features appearing before some AI solution takes over the task of writing code, I'm not holding my breath...

@CyrusNajmabadi
Comment options

I'd love to see both of these implemented. There doesn't seem any real interest in the language team

This is incorrect. The language team is interested in these cases.

As for shapes, extension everything and all those other much promised utopia features appearing before some AI solution takes over the task of writing code, I'm not holding my breath...

We're currently in the prototyping stage of those features. So it's further along than most other language proposals.

@walczakb
Comment options

There is one more natural use case—collection expressions inparams, which would basically allow skipping the brackets, e.g.:

Method(0..10,20..30,100);voidMethod(paramsint[]args){Something(args);}

The wayparams currently works can also be thought of as skipping the brackets (e.g.,Method(0, 1, 2) instead ofMethod([0, 1, 2])), so the above would just make the two features more consistent with each other.

Comment options

line.Split([',', ';', '\n'], StringSplitOptions.RemoveEmptyEntries);// CS0121 The call is ambiguous between the following methods or properties: 'string.Split(char[]?, StringSplitOptions)' and 'string.Split(string?, StringSplitOptions)'

What's the rationale here? Like can a collection expression be assigned to string in any circumstance?

You must be logged in to vote
7 replies
@CyrusNajmabadi
Comment options

@TahirAhmadov having string be a supported target type does not mean overloads like this would not work.

I personally never ever had to create a string from a char collection in almost 20 years.

i've def wanted this. but it's mostly just about consistency. in the language, a string is already a collection of chars (and already works with linq/foreach/etc. uniformly like other collections). Having it work as a target just means it works fine in the other direction.

Note: this is not to say that it is equivalent in priority to a char[]/span target.

@TahirAhmadov
Comment options

Do you mean that the behavior@Tinister experienced (according to his comment) is an preview bug and that it will work as expected later?

@CyrusNajmabadi
Comment options

it is the expected behavior in c#12. we explicitly carved it out so we can make it work in c# 13.

@Tinister
Comment options

Why isline.Split([',', ';', '\n']) not ambiguous whenline.Split(new char[] { ',', ';', '\n' }) andline.Split(",;\n") are both valid call sites?

@yaakov-h
Comment options

@Tinister one isstring.Split(char[]) and the other isstring.Split(string, StringSplitOptions = StringSplitOptions.None). The overload resolution rules chooseSplit(char[]) as the better overload rather than using the one with the default parameter, making this non-ambiguous.

Comment options

There seems to be a lack of a default type now, consider the following scenario

publicclassA{publicstaticimplicitoperatorA(int[]source)=>thrownewNotImplementedException();//...}// Suppose there's a method accepting an argument of type AvoidM(Aa)=>thrownewNotImplementedException();// Current attempt at calling the methodvoidM2(){M([1,2,3]);// Produces error CS1503}// The correct way to call it currentlyint[]x1={1,2,3};M(x1);// This works correctly
You must be logged in to vote
2 replies
@huoyaoyuan
Comment options

Default type is intentionally not implemented for C# 12. The team is still deciding the correct approach for C# 13.

@CyrusNajmabadi
Comment options

Note; this is not really a case of a default type. This is more a case of the language not "seeing through" implicit operators. It's soemthing we could consider, and then you could use collection exprs anywhere where there was an implicit operator and the source-type itself was something that could be constructed by collection exprs.

Comment options

Have you considered adding features like 'index functions'? If we have such functions, we only need to provide the expression of this function and the length to construct an array in this form before compilation. However, after compilation, it can be an array with a fixed length and each element is also determined.

You must be logged in to vote
7 replies
@HaloFour
Comment options

That feels like a feature much better suited to a helper method or via LINQ, especially since it would be something that could only be evaluated at compile-time in the most limited of circumstances. Collection literals aren't intended to be another form of comprehension syntax.

@nczsl
Comment options

/*Of course, we can go further like Python. We can replace the position of "length" with another collection, even if their types are not the same, as long as the index function can calculate it.*/int[]x1=[i=>2*i,5];float[]x2=[i=>(i+3)/2,x1];//ok
@huoyaoyuan
Comment options

This is already valid:

int[]x1=[..fromiinEnumerable.Range(5)select2*i];
@huoyaoyuan
Comment options

See#7634

@nczsl
Comment options

/*However, compared to the LINQ expression you provided, my solution obviously has more room for optimization and  performance advantages. Because the context variable 'i' here is directly defined by the array expression environment, and yours is provided by the LINQ extension method, and the extension method is used multiple times from the 'from' clause expansion to 'select' selection, etc. Moreover, I think such syntax is supported by C# after the collection expansion operator, and I don't see anything special about the separate issue you provided; and the index function is a mapping based on array indexing, with clear semantics.*/int[]x1=[..fromiinEnumerable.Range(5)select2*i];// This can hardly be considered a new proposal because since the expansion operation of '..' no matter what, you can at least do this:int[]x1=[..(fromiinEnumerable.Range(5)select2*i).ToArray()];/*So actually no matter how you write it, as long as it satisfies two points, one, it is an expression, and two, it returns an array. After satisfying these two points, using the expansion operator can make it be constructed into an array; so the existence value of this index function expression I mentioned may not be that high. Although it looks more concise and clear, it is indeed not something completely new that cannot be expressed with old technology. This is also worth discussing and thinking about, to see if there is a need for the index function to exist.*/
Comment options

I'd love to be able to use collection expression directly inforeach. Currently,

foreach (string s in ["a", "b"]) { ... }

yieldsCS9176: There is no target type for the collection expression..


Currently known workarounds:

string[] = ["a", "b"];foreach (string s in values) { ... }

or

foreach (string s in new[] {"a", "b"}) { ... }

Notes:

  • Making this a language feature would allow the compiler to choose the most suitable/efficient type for "enumerating over".
  • There's not many things I miss from VB.NET, butFor Each s In {"a", "b"} is one of them. ;-)
You must be logged in to vote
1 reply
@jnm2
Comment options

jnm2Apr 15, 2024
Collaborator

Comment options

One of the things that bug me with collection expressions is the following:

this worksvar ar = new[] { "string" };
this doesn'tvar ar2 = ["string"];
image

If the argument is, because what type should it be if you passvar ar2 = ["string", 1]; I would say the result should be the same as when you dovar ar = new[] { "string", 1}; namely that it complains that the best type can't be determined:
image

You must be logged in to vote
1 reply
@huoyaoyuan
Comment options

The question is not aboutelement type, but thecontainer type.

There are different type of collections in .NET. Array, list and spans are all having unique advantages. We didn't have a conclusion to prefer any one.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Labels
None yet
24 participants
@KathleenDollard@DavidArno@glen-84@yaakov-h@stephentoub@GiottoVerducci@CyrusNajmabadi@nczsl@huoyaoyuan@BlinkSun@vriesdea@jnm2@HaloFour@Tinister@TonyValenti@sab39@TahirAhmadov@KennethHoff@HeinziAT@deMD@RNablaand others

[8]ページ先頭

©2009-2025 Movatter.jp