Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Implement generic interfaces on Regex collections #1756

Merged
merged 1 commit into from Jun 6, 2015

Conversation

justinvp
Copy link
Contributor

Implement IList<T>, IReadOnlyList<T>, and IList on the Regex collections.

Fixes #271

{
if (array == null)
throw new ArgumentNullException("array");
if (arrayIndex < 0 || arrayIndex > array.Length)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it be arrayIndex >= array.Length?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally had it like that, but then the behavior differs from List<T> and Dictionary<TKey, TValue>, which throws ArgumentException in that case instead of ArgumentOutOfRangeException.

See https://github.com/dotnet/coreclr/blob/ef1e2ab328087c61a6878c1e84f4fc5d710aebce/src/mscorlib/src/System/Collections/Generic/Dictionary.cs#L248

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's actually even a behavioral difference between List<T>.CopyTo and Dictionary<TKey, TValue>.CopyTo.

The following tests pass:

ICollection<int> list = new List<int> { 0, 1 };
int[] listArray = new int[2];
Assert.Throws<ArgumentOutOfRangeException>(() => list.CopyTo(listArray, -1));
Assert.Throws<ArgumentException>(() => list.CopyTo(listArray, 2));
Assert.Throws<ArgumentException>(() => list.CopyTo(listArray, 3));

ICollection<KeyValuePair<int, int>> dictionary = new Dictionary<int, int> { { 0, 0 }, { 1, 1 } };
KeyValuePair<int, int>[] dictionaryArray = new KeyValuePair<int, int>[2];
Assert.Throws<ArgumentOutOfRangeException>(() => dictionary.CopyTo(dictionaryArray, -1));
Assert.Throws<ArgumentException>(() => dictionary.CopyTo(dictionaryArray, 2));
Assert.Throws<ArgumentOutOfRangeException>(() => dictionary.CopyTo(dictionaryArray, 3));

Notice the last case is ArgumentException for List<T> but ArgumentOutOfRangeException for Dictionary<TKey, TValue>

Right now CaptureCollection and GroupCollection are consistent with Dictionary<TKey, TValue> whereas MatchCollection is consistent with List<T> (because MatchCollection uses List<T>.CopyTo internally).

Maybe it'd be better if I remove the arrayIndex > array.Length check altogether from CaptureCollection and GroupCollection, so the exceptions are consistent with MatchCollection and List<T>. Or not worry about it too much because it's all ArgumentException anyway?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are already not very consistent, I would do either what you suggested (to simplify the code) or what I suggested (follow current convention and implemented correctly). I would not try to be consistent with incorrectly implemented range checks ;-)

Moreover, it would be great to have a helper for common range checks, so that people don't make similar mistakes (to those made in the existing collections). Something that can be called along the lines of:
Precondition.RequireFit(source : this, destination : array, destinationIndex : arrayIndex);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After investigating how other collections implement CopyTo, I think we should leave the range checks in the PR as-is as this is how other collections implement CopyTo.

For example, all of the following cases do not throw:

new int[0].CopyTo(new int[10], 10);
new List<int>().CopyTo(new int[10], 10);
new LinkedList<int>().CopyTo(new int[10], 10);
new Queue<int>().CopyTo(new int[10], 10);
new Stack<int>().CopyTo(new int[10], 10);
new HashSet<int>().CopyTo(new int[10], 10);
new SortedSet<int>().CopyTo(new int[10], 10);
((ICollection<KeyValuePair<int, int>>)new Dictionary<int, int>()).CopyTo(new KeyValuePair<int,int>[10], 10);
((ICollection<KeyValuePair<int, int>>)new SortedDictionary<int, int>()).CopyTo(new KeyValuePair<int, int>[10], 10);
new ConcurrentStack<int>().CopyTo(new int[10], 10);
new ConcurrentQueue<int>().CopyTo(new int[10], 10);
new ConcurrentBag<int>().CopyTo(new int[10], 10);
((ICollection<KeyValuePair<int, int>>)new ConcurrentDictionary<int, int>()).CopyTo(new KeyValuePair<int, int>[10], 10);
ImmutableArray.Create<int>().CopyTo(new int[10], 10);
ImmutableList.Create<int>().CopyTo(new int[10], 10);
((ICollection<int>)ImmutableHashSet.Create<int>()).CopyTo(new int[10], 10);
((ICollection<int>)ImmutableSortedSet.Create<int>()).CopyTo(new int[10], 10);
((ICollection<KeyValuePair<int, int>>)ImmutableDictionary.Create<int, int>()).CopyTo(new KeyValuePair<int, int>[10], 10);
((ICollection<KeyValuePair<int, int>>)ImmutableSortedDictionary.Create<int, int>()).CopyTo(new KeyValuePair<int, int>[10], 10);

If I change the range check from arrayIndex > array.Length to arrayIndex >= array.Length, then the regex collections would throw in such cases and be inconsistent with all of these collections.

@KrzysztofCwalina
Copy link
Member

@dotnet-bot test this please

@stephentoub
Copy link
Member

@weshaggard / @mmitche, it seems like all PRs against the future branch are failing in CI, I believe for issues that were already fixed in master, and I'm wondering if merging master to future would help...?

@stephentoub
Copy link
Member

@justinvp, master was just merged to future, in part because there were some fixes in master from a while ago that I believe were causing your PR's CIs to fail. There are now merge conflicts with your change. Can you fix it up appropriately and recommit/push?

@justinvp justinvp force-pushed the regex_generic_collections branch 2 times, most recently from 2043924 to 3664fc9 Compare June 3, 2015 07:36
@justinvp
Copy link
Contributor Author

justinvp commented Jun 3, 2015

Fixed. The conflict was due to the move from packages.config to project.json.

After running a build, the project.lock.json file becomes modified. Should these modifications to project.lock.json be checked-in and included in the PR?

After the changes to project.json and running msbuild \t:BuildAndTest from the test directory, it builds and the tests pass, but I get a build warning:

"C:\dev\corefx\src\System.Text.RegularExpressions\tests\System.Text.RegularExpressions.Tests.csproj" (BuildAndTest target) (1) ->(ResolveAssemblyReferences target) -> C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Common.CurrentVersion.targets(1697,5): warning MSB3277: Found conflicts between different versions of the same dependent assembly that could not be resolved. These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\dev\corefx\src\System.Text.RegularExpressions\tests\System.Text.RegularExpressions.Tests.csproj]

I'm not sure where in the build log to look for the reference conflicts it is warning about, or why it's warning about them.

I see the same warning for System.Security.Principal.Windows.Tests.csproj when running msbuild \t:BuildAndTest for it out of the future branch (without any of the changes from this PR):

"C:\dev\corefx\src\System.Security.Principal.Windows\tests\System.Security.Principal.Windows.Tests.csproj" (BuildAndTest target) (1) ->(ResolveAssemblyReferences target) -> C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Common.CurrentVersion.targets(1697,5): warning MSB3277: Found conflicts between different versions of the same dependent assembly that could not be resolved. These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\dev\corefx\src\System.Security.Principal.Windows\tests\System.Security.Principal.Windows.Tests.csproj]

@stephentoub
Copy link
Member

@ericstj, the answers to @justinvp's questions are "the changes to project.lock.json should be checked in" and "the warning is expected", correct?


bool ICollection<Capture>.Contains(Capture item)
{
var comparer = EqualityComparer<Capture>.Default;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than duplicate logic, did you consider just having the implementation be:

return ((IList<Capture>)this).IndexOf(item) >= 0;

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed throughout.

@stephentoub
Copy link
Member

A few minor comments, but otherwise LGTM. Thanks for doing this, Justin.

Implement IList<T>, IReadOnlyList<T>, and IList on the Regex collections.
@justinvp
Copy link
Contributor Author

justinvp commented Jun 4, 2015

I updated the PR to include project.lock.json and changed Contains to be implemented in terms of IndexOf.

Thanks for the review, @KrzysztofCwalina and @stephentoub!

@KrzysztofCwalina
Copy link
Member

Yeah. Thanks for the contribution. I will start new rounds of tests and merge when they pass.

@KrzysztofCwalina
Copy link
Member

@dotnet-bot test this please

@justinvp
Copy link
Contributor Author

justinvp commented Jun 4, 2015

Unrelated test failure on linux:

09:18:55 System.IO.Compression.Test.zip_InvalidParametersAndStrangeFiles.StrangeFiles4 [FAIL]
09:18:55 Assert.Equal() Failure
09:18:55 ��� (pos 0)
09:18:55 Expected: second.txt
09:18:55 Actual: notempty\second.txt
09:18:55 ��� (pos 0)
09:18:55 Stack Trace:
09:18:55 at System.IO.Compression.Test.ZipTest.IsZipSameAsDir(Stream archiveFile, String directory, ZipArchiveMode mode, Boolean dontRequireExplicit, Boolean dontCheckTimes)
09:18:55 at System.IO.Compression.Test.zip_InvalidParametersAndStrangeFiles.d__58.MoveNext()
09:18:55 --- End of stack trace from previous location where exception was thrown ---
09:18:55 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
09:18:55 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
09:18:55 --- End of stack trace from previous location where exception was thrown ---
09:18:55 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
09:18:55 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
09:18:55 --- End of stack trace from previous location where exception was thrown ---
09:18:55 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
09:18:55 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
09:19:04 Finished: System.IO.Compression.Tests
09:19:04
09:19:04 === TEST EXECUTION SUMMARY ===
09:19:04 System.IO.Compression.Tests Total: 75, Errors: 0, Failed: 1, Skipped: 0, Time: 10.114s

Looks like https://github.com/dotnet/corefx/issues/1904

#1905 needs to be merged into future.

@stephentoub
Copy link
Member

#1905 needs to be merged into future.

Yup, that should fix it.

@KrzysztofCwalina
Copy link
Member

@dotnet-bot test this please

@stephentoub
Copy link
Member

@justinvp, the future branch is now up-to-date with master.

@justinvp
Copy link
Contributor Author

justinvp commented Jun 6, 2015

@stephentoub, thanks! Does @dotnet-bot accept commands from me?...

@justinvp
Copy link
Contributor Author

justinvp commented Jun 6, 2015

@dotnet-bot test this please

@stephentoub
Copy link
Member

Apparently 😄

@stephentoub
Copy link
Member

@dotnet-bot test this please

@stephentoub
Copy link
Member

Thanks for the contribution, Justin!

stephentoub added a commit that referenced this pull request Jun 6, 2015
Implement generic interfaces on Regex collections
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
8 participants