{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/corefx/issues/10752","repository_url":"https://api.github.com/repos/dotnet/corefx","labels_url":"https://api.github.com/repos/dotnet/corefx/issues/10752/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/corefx/issues/10752/comments","events_url":"https://api.github.com/repos/dotnet/corefx/issues/10752/events","html_url":"https://github.com/dotnet/corefx/issues/10752","id":170996032,"node_id":"MDU6SXNzdWUxNzA5OTYwMzI=","number":10752,"title":"Collection<T> and ObservableCollection<T> do not support ranges","user":{"login":"robertmclaws","id":1657085,"node_id":"MDQ6VXNlcjE2NTcwODU=","avatar_url":"https://avatars3.githubusercontent.com/u/1657085?v=4","gravatar_id":"","url":"https://api.github.com/users/robertmclaws","html_url":"https://github.com/robertmclaws","followers_url":"https://api.github.com/users/robertmclaws/followers","following_url":"https://api.github.com/users/robertmclaws/following{/other_user}","gists_url":"https://api.github.com/users/robertmclaws/gists{/gist_id}","starred_url":"https://api.github.com/users/robertmclaws/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/robertmclaws/subscriptions","organizations_url":"https://api.github.com/users/robertmclaws/orgs","repos_url":"https://api.github.com/users/robertmclaws/repos","events_url":"https://api.github.com/users/robertmclaws/events{/privacy}","received_events_url":"https://api.github.com/users/robertmclaws/received_events","type":"User","site_admin":false},"labels":[{"id":165998434,"node_id":"MDU6TGFiZWwxNjU5OTg0MzQ=","url":"https://api.github.com/repos/dotnet/corefx/labels/api-ready-for-review","name":"api-ready-for-review","color":"5319e7","default":false},{"id":154923328,"node_id":"MDU6TGFiZWwxNTQ5MjMzMjg=","url":"https://api.github.com/repos/dotnet/corefx/labels/area-System.Collections","name":"area-System.Collections","color":"d4c5f9","default":false},{"id":145399958,"node_id":"MDU6TGFiZWwxNDUzOTk5NTg=","url":"https://api.github.com/repos/dotnet/corefx/labels/up-for-grabs","name":"up-for-grabs","color":"159818","default":false}],"state":"open","locked":false,"assignee":{"login":"robertmclaws","id":1657085,"node_id":"MDQ6VXNlcjE2NTcwODU=","avatar_url":"https://avatars3.githubusercontent.com/u/1657085?v=4","gravatar_id":"","url":"https://api.github.com/users/robertmclaws","html_url":"https://github.com/robertmclaws","followers_url":"https://api.github.com/users/robertmclaws/followers","following_url":"https://api.github.com/users/robertmclaws/following{/other_user}","gists_url":"https://api.github.com/users/robertmclaws/gists{/gist_id}","starred_url":"https://api.github.com/users/robertmclaws/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/robertmclaws/subscriptions","organizations_url":"https://api.github.com/users/robertmclaws/orgs","repos_url":"https://api.github.com/users/robertmclaws/repos","events_url":"https://api.github.com/users/robertmclaws/events{/privacy}","received_events_url":"https://api.github.com/users/robertmclaws/received_events","type":"User","site_admin":false},"assignees":[{"login":"robertmclaws","id":1657085,"node_id":"MDQ6VXNlcjE2NTcwODU=","avatar_url":"https://avatars3.githubusercontent.com/u/1657085?v=4","gravatar_id":"","url":"https://api.github.com/users/robertmclaws","html_url":"https://github.com/robertmclaws","followers_url":"https://api.github.com/users/robertmclaws/followers","following_url":"https://api.github.com/users/robertmclaws/following{/other_user}","gists_url":"https://api.github.com/users/robertmclaws/gists{/gist_id}","starred_url":"https://api.github.com/users/robertmclaws/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/robertmclaws/subscriptions","organizations_url":"https://api.github.com/users/robertmclaws/orgs","repos_url":"https://api.github.com/users/robertmclaws/repos","events_url":"https://api.github.com/users/robertmclaws/events{/privacy}","received_events_url":"https://api.github.com/users/robertmclaws/received_events","type":"User","site_admin":false},{"login":"safern","id":22899328,"node_id":"MDQ6VXNlcjIyODk5MzI4","avatar_url":"https://avatars1.githubusercontent.com/u/22899328?v=4","gravatar_id":"","url":"https://api.github.com/users/safern","html_url":"https://github.com/safern","followers_url":"https://api.github.com/users/safern/followers","following_url":"https://api.github.com/users/safern/following{/other_user}","gists_url":"https://api.github.com/users/safern/gists{/gist_id}","starred_url":"https://api.github.com/users/safern/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/safern/subscriptions","organizations_url":"https://api.github.com/users/safern/orgs","repos_url":"https://api.github.com/users/safern/repos","events_url":"https://api.github.com/users/safern/events{/privacy}","received_events_url":"https://api.github.com/users/safern/received_events","type":"User","site_admin":false}],"milestone":{"url":"https://api.github.com/repos/dotnet/corefx/milestones/21","html_url":"https://github.com/dotnet/corefx/milestone/21","labels_url":"https://api.github.com/repos/dotnet/corefx/milestones/21/labels","id":3242632,"node_id":"MDk6TWlsZXN0b25lMzI0MjYzMg==","number":21,"title":"3.0","description":".NET Core 3.0, next major release after 2.1. See [official release dates](https://github.com/dotnet/core/blob/master/roadmap.md) for details.\r\n\r\nWe will move important issues from Future to 3.0 as the priorities and dates for next release become more clear.","creator":{"login":"karelz","id":5776068,"node_id":"MDQ6VXNlcjU3NzYwNjg=","avatar_url":"https://avatars2.githubusercontent.com/u/5776068?v=4","gravatar_id":"","url":"https://api.github.com/users/karelz","html_url":"https://github.com/karelz","followers_url":"https://api.github.com/users/karelz/followers","following_url":"https://api.github.com/users/karelz/following{/other_user}","gists_url":"https://api.github.com/users/karelz/gists{/gist_id}","starred_url":"https://api.github.com/users/karelz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/karelz/subscriptions","organizations_url":"https://api.github.com/users/karelz/orgs","repos_url":"https://api.github.com/users/karelz/repos","events_url":"https://api.github.com/users/karelz/events{/privacy}","received_events_url":"https://api.github.com/users/karelz/received_events","type":"User","site_admin":false},"open_issues":243,"closed_issues":2400,"state":"open","created_at":"2018-04-04T16:40:10Z","updated_at":"2018-10-09T18:06:23Z","due_on":null,"closed_at":null},"comments":128,"created_at":"2016-08-13T06:31:14Z","updated_at":"2018-10-09T19:03:58Z","closed_at":null,"author_association":"NONE","body":"# Update 10/04/2018\r\n@ianhays and I discussed this and we agree to add this 6 APIs for now:\r\n```cs\r\n    // Adds a range to the end of the collection.\r\n    // Raises CollectionChanged (NotifyCollectionChangedAction.Add)\r\n    public void AddRange(IEnumerable<T> collection) => InsertItemsRange(0, collection);\r\n\r\n    // Inserts a range\r\n    // Raises CollectionChanged (NotifyCollectionChangedAction.Add)\r\n    public void InsertRange(int index, IEnumerable<T> collection) => InsertItemsRange(index, collection);\r\n\r\n    // Removes a range.\r\n    // Raises CollectionChanged (NotifyCollectionChangedAction.Remove)\r\n    public void RemoveRange(int index, int count) => RemoveItemsRange(index, count);\r\n\r\n    // Will allow to replace a range with fewer, equal, or more items.\r\n    // Raises CollectionChanged (NotifyCollectionChangedAction.Replace)\r\n    public void ReplaceRange(int index, int count, IEnumerable<T> collection)\r\n    {\r\n         RemoveItemsRange(index, count);\r\n         InsertItemsRange(index, collection);\r\n    }\r\n\r\n    #region virtual methods\r\n    protected virtual void InsertItemsRange(int index, IEnumerable<T> collection);\r\n    protected virtual void RemoveItemsRange(int index, int count);\r\n    #endregion\r\n```\r\n\r\nAs those are the most commonly used across collection types and the `Predicate` ones can be achieved through Linq and seem like edge cases.\r\n\r\nTo answer @terrajobst questions:\r\n> Should the methods be virtual? If no, why not? If yes, how does eventing work and how do derived types work?\r\n\r\nYes, we would like to introduce 2 protected virtual methods to stick with the current pattern that we follow with other Insert/Remove apis to give people hability to add their custom removals (like filtering items on a certain condition).\r\n\r\n> Should some of these methods be pushed down to Collection<T>?\r\n\r\nYes, and then `ObservableCollection` could just call the base implementation and then trigger the necessary events.\r\n \r\nLet's keep the final speclet at the top for easier search\r\n# Speclet (Updated 9/23/2016)\r\n## Scope\r\n\r\nModernize `Collection<T>` and `ObservableCollection<T>` by allowing them to handle operations against multiple items simultaneously.\r\n## Rationale\r\n\r\nThe `ObservableCollection` is a critical collection when it comes to XAML-based development, though it can also be useful when building API client libraries as well. Because it implements `INotifyPropertyChanged` and `INotifyCollectionChanged`, nearly every XAML app in existence uses some form of this collection to bind a set of objects against UI.\r\n\r\nHowever, this class has some shortcomings. Namely, it cannot currently handle adding or removing multiple objects in a single call. Because of that, it also cannot manipulate the collection in such a way that the `PropertyChanged` events are raised at the very end of the operation.\r\n\r\nConsider the following situation: \r\n- You have a XAML app that accesses an API.\r\n- That API call returns 25 objects that need to be bound to the UI.\r\n- In order to get the data displayed into the UI, you likely have to cycle through the results, and add them one at a time to the ObservableCollection. \r\n- This has the side-effect of firing the `CollectionChanged` event 25 times. If you are also using that event to do other processing on incoming items, then those events are firing 25 times too. This can get very expensive, very quickly.\r\n- Additionally, that event will have `ChangedItems` Lists that will only ever have 0 or 1 objects in them. That is... not ideal.\r\n\r\nThis behavior is unnecessary, especially considering that `NotifyCollectionChangedEventArgs` already has the components necessary to handle firing the event once for multiple items, but that capability is presently not being used at all.\r\n\r\nImplementing this properly would allow for better performance in these types of apps, and would negate the need for the plethora of replacements out there ([here](http://blog.ddeysel.com/technicals/introducing-the-awesome-observable-collection/), [here](http://tliangnet.blogspot.com/2013/04/observablecollection-performance-issue.html), and [here](https://github.com/Windows-XAML/Template10/blob/master/Template10%20%28Library%29/Utils/IEnumerableUtils.cs#L34-L45), for example).\r\n## Usage\r\n\r\nGiven the above scenario as an example, usage would look like this pseudocode:\r\n\r\n``` cs\r\n    var observable = new ObservableCollection<SomeObject>();\r\n    var client = new HttpClient();\r\n    var result = client.GetStringAsync(\"http://someapi.com/someobject\");\r\n    var results = JsonConvert.DeserializeObject<SomeObject>(result);\r\n    observable.AddRange(results);\r\n```\r\n## Implementation\r\n\r\nThis is not the complete implementation, because other `*Range` functionality would need to be implemented as well. You can see the start of this work in PR #10751\r\n\r\n``` cs\r\n\r\n    // Adds a range to the end of the collection.\r\n    // Raises CollectionChanged (NotifyCollectionChangedAction.Add)\r\n    public void AddRange(IEnumerable<T> collection)\r\n\r\n    // Inserts a range\r\n    // Raises CollectionChanged (NotifyCollectionChangedAction.Add)\r\n    public void InsertRange(int index, IEnumerable<T> collection);\r\n\r\n    // Removes a range.\r\n    // Raises CollectionChanged (NotifyCollectionChangedAction.Remove)\r\n    public void RemoveRange(int index, int count);\r\n\r\n    // Will allow to replace a range with fewer, equal, or more items.\r\n    // Raises CollectionChanged (NotifyCollectionChangedAction.Replace)\r\n    public void ReplaceRange(int index, int count, IEnumerable<T> collection);\r\n\r\n    // Removes any item that matches the search criteria.\r\n    // Raises CollectionChanged (NotifyCollectionChangedAction.Remove)\r\n    // RWM: Excluded for now, will see if possible to add back in after implementation and testing.\r\n    // public int RemoveAll(Predicate<T> match);\r\n```\r\n## Obstacles\r\n\r\nDoing this properly, and having the methods intuitively named, could potentially have the side effect of breaking existing classes that inherit from `ObservableCollection` to solve this problem. A good way to test this would be to make the change, compile something like Template10 against this new assembly, and see if it breaks.\r\n\r\n---\r\n\r\nSo the `ObservableCollection` is one of the cornerstones of software development, not just in Windows, but on the web. One issue that comes up constantly is that, while the `OnCollectionChanged` event has a structure and constructors that support signaling the change for multiple items being added, the `ObservableCollection` does not have a method to support this.\r\n\r\nIf you look at the web as an example, Knockout has a way to be able to add multiple items to the collection, but not signal the change until the very end. The `ObservableCollection` needs the same functionality, but does not have it.\r\n\r\nIf you look at other extension methods to solve this problem, like the one in [Template10](https://github.com/Windows-XAML/Template10/blob/master/Template10%20%28Library%29/Utils/IEnumerableUtils.cs#L34-L45), they let you add multiple items, but do not solve the signaling problem. That's because the `ObservableCollection.InsertItem()` method overrides `Collection.InsertItem()`, and all of the other methods are private. So the only way to fix this properly is in the `ObservableCollection` itself.\r\n\r\nI'm proposing an \"AddRange\" function that accepts an existing collection as input, optionally clears the collection before adding, and then throws the `OnCollectionChanging` event AFTER all the objects have been added. I have already implemented this in a PR #10751 so you can see what the implementation would look like.\r\n\r\nI look forward to your feedback. Thanks!\r\n"},"comment":{"url":"https://api.github.com/repos/dotnet/corefx/issues/comments/428312248","html_url":"https://github.com/dotnet/corefx/issues/10752#issuecomment-428312248","issue_url":"https://api.github.com/repos/dotnet/corefx/issues/10752","id":428312248,"node_id":"MDEyOklzc3VlQ29tbWVudDQyODMxMjI0OA==","user":{"login":"terrajobst","id":5169960,"node_id":"MDQ6VXNlcjUxNjk5NjA=","avatar_url":"https://avatars0.githubusercontent.com/u/5169960?v=4","gravatar_id":"","url":"https://api.github.com/users/terrajobst","html_url":"https://github.com/terrajobst","followers_url":"https://api.github.com/users/terrajobst/followers","following_url":"https://api.github.com/users/terrajobst/following{/other_user}","gists_url":"https://api.github.com/users/terrajobst/gists{/gist_id}","starred_url":"https://api.github.com/users/terrajobst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/terrajobst/subscriptions","organizations_url":"https://api.github.com/users/terrajobst/orgs","repos_url":"https://api.github.com/users/terrajobst/repos","events_url":"https://api.github.com/users/terrajobst/events{/privacy}","received_events_url":"https://api.github.com/users/terrajobst/received_events","type":"User","site_admin":false},"created_at":"2018-10-09T19:03:58Z","updated_at":"2018-10-09T19:03:58Z","author_association":"MEMBER","body":"Looks good, but we should add a `ReplaceItemsRange`:\r\n\r\n```csharp\r\npublic partial class Collection<T>\r\n{\r\n    public void AddRange(IEnumerable<T> collection);\r\n\r\n    public void InsertRange(int index, IEnumerable<T> collection);\r\n\r\n    public void RemoveRange(int index, int count);\r\n\r\n    public void ReplaceRange(int index, int count, IEnumerable<T> collection);\r\n\r\n    // These are override by ObservableCollection<T> to raise the event:\r\n\r\n    protected virtual void InsertItemsRange(int index, IEnumerable<T> collection);\r\n\r\n    protected virtual void RemoveItemsRange(int index, int count);\r\n\r\n    protected virtual void ReplaceItemsRange(int index, int count, IEnumerable<T> collection);\r\n}\r\n```\r\n"}}
{"actor":{"display_login":"terrajobst"}}