{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/corefx/issues/25220","repository_url":"https://api.github.com/repos/dotnet/corefx","labels_url":"https://api.github.com/repos/dotnet/corefx/issues/25220/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/corefx/issues/25220/comments","events_url":"https://api.github.com/repos/dotnet/corefx/issues/25220/events","html_url":"https://github.com/dotnet/corefx/issues/25220","id":273466720,"number":25220,"title":"Generic Range type","user":{"login":"YohDeadfall","id":10694013,"avatar_url":"https://avatars0.githubusercontent.com/u/10694013?v=4","gravatar_id":"","url":"https://api.github.com/users/YohDeadfall","html_url":"https://github.com/YohDeadfall","followers_url":"https://api.github.com/users/YohDeadfall/followers","following_url":"https://api.github.com/users/YohDeadfall/following{/other_user}","gists_url":"https://api.github.com/users/YohDeadfall/gists{/gist_id}","starred_url":"https://api.github.com/users/YohDeadfall/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/YohDeadfall/subscriptions","organizations_url":"https://api.github.com/users/YohDeadfall/orgs","repos_url":"https://api.github.com/users/YohDeadfall/repos","events_url":"https://api.github.com/users/YohDeadfall/events{/privacy}","received_events_url":"https://api.github.com/users/YohDeadfall/received_events","type":"User","site_admin":false},"labels":[{"id":703517252,"url":"https://api.github.com/repos/dotnet/corefx/labels/api-suggestion","name":"api-suggestion","color":"1d76db","default":false},{"id":158885797,"url":"https://api.github.com/repos/dotnet/corefx/labels/area-Meta","name":"area-Meta","color":"d4c5f9","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":4,"created_at":"2017-11-13T15:20:35Z","updated_at":"2017-11-16T17:31:36Z","closed_at":null,"author_association":"NONE","body":"Currently there is the non-generic `Range` type in the corefxlab repository ([link](https://github.com/dotnet/corefxlab/blob/2711e0824729fb318eb4e25544236bb2f53fa33e/src/System.Buffers.Experimental/System/Range.cs#L5-L20)) which is designed for a new C# slice syntax. As you can see, it's limited only to `int` values.\r\n\r\n```csharp\r\npublic readonly struct Range\r\n{\r\n   public readonly long Index;\r\n   public readonly long Length;\r\n}\r\n\r\npublic readonly ref struct Span<T>\r\n{\r\n    public Span<T> Slice(Range range) => Slice(range.Index, range.Length);\r\n    public Span<T> Slice(int index, int length) { throw null; }\r\n}\r\n```\r\n```csharp\r\nvar buffer = new Span<int>();\r\nvar slice1 = buffer[3..4]; // with syntax sugar\r\nvar slice2 = buffer.Slice(new Range(3, 4 - 3)); // without it\r\n```\r\nSo it would be nice to have a generic one instead it to represent ranges of different types.\r\n\r\n## Rationale and Usage\r\n\r\nImplementing the generic version would allow to have a common type to represent ranges. There are too many areas where it could be used including numeric processing, range based operations, and simply to store intervals (for example `Range<Time> OpenHours`).\r\n\r\n## Proposed API\r\n\r\n```csharp\r\n[Serializable]\r\npublic readonly struct Range<T> : IEquatable<Range<T>>, IFormattable, ISerializable\r\n    where T : IComparable<T>\r\n{\r\n    public readonly T From;\r\n    public readonly T To;\r\n\r\n    public Range(T from, T to) { throw null; }\r\n    private Range(SerializationInfo info, StreamingContext context) { throw null; }\r\n\r\n    public void Deconstruct(out T from, out T to) { throw null; }\r\n\r\n    public override bool Equals(object obj) { throw null; }\r\n    public bool Equals(Range<T> other) { throw null; }\r\n    public override int GetHashCode() { throw null; }\r\n\r\n    public override string ToString() { throw null; }\r\n    public override string ToString(string forma) { throw null; }\r\n    public override string ToString(string format, IFormatProvider formatProvider) { throw null; }\r\n\r\n    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { throw null; }\r\n}\r\n\r\npublic static class Range\r\n{\r\n    // all methods should be aggressively inlined\r\n    public static bool IsNormalized<T>(this Range<T> range) where T : IComparable<T> { throw null; }\r\n    public static Range<T> Normalize<T>(this Range<T> range) where T : IComparable<T> { throw null; }\r\n\r\n    public static bool IsEmpty<T>(this Range<T> range) where T : IComparable<T> { throw null; }\r\n    public static bool Contains<T>(this Range<T> range, T value) where T : IComparable<T> { throw null; }\r\n    public static bool Intersects(this Range<T> range, Range<T> other) where T : IComparable<T> { throw null; }\r\n    public static Range<T> Intersect(this Range<T> range, Range<T> other) where T : IComparable<T> { throw null; }\r\n\r\n    // Specialized versions of the previous methods for numeric and time types\r\n    public static bool IsNormalized(this Range<int> range) { throw null; }\r\n\r\n    // Only for numeric and time types\r\n    public static int Length(this Range<int> range) { throw null; }\r\n}\r\n```\r\n\r\n## Details\r\n\r\nThere is no `struct` constraint to support `string` because it's a good candidate for range based operations, but there are methods which accept `IComparable<T>` as the second parameter should exist to do culture specific actions.\r\n\r\n```csharp\r\nvar range = new Range<string>(\"A\", \"a\");\r\nvar emptyCS = range.IsEmpty(StringComparer.InvariantCulture); // false\r\nvar emptyCI = range.IsEmpty(StringComparer.InvariantCultureIgnoreCase); // true\r\n```\r\n\r\nThe implementations wouldn't conflict with the syntax sugar for slices and would not hurt performance because the length computation is inlined:\r\n```csharp\r\npublic readonly ref struct Span<T>\r\n{\r\n    public Span<T> Slice(Range<int> range) => Slice(range.From, range.Length());\r\n    public Span<T> Slice(int index, int length) { throw null; }\r\n}\r\n```\r\n\r\n```csharp\r\nvar buffer = new Span<int>();\r\nvar slice1 = buffer[3..4];\r\nvar slice2 = buffer.Slice(new Range<int>(3, 4)); // without it\r\n```\r\n\r\nIt's possible to implement `IsEmpty`, `IsNormalized` and other things as instance members using generic type parameter checks, but that will cause poor performance for non-BCL types.\r\n\r\n### Benchmark\r\n\r\n```csharp\r\npublic static class Range\r\n{\r\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\r\n    public static bool IsNormalizedSpecialized(this Range<long> range)\r\n        => range.To >= range.From;\r\n\r\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\r\n    public static bool IsNormalizedGeneric<T>(this Range<T> range)\r\n        where T : IComparable<T>\r\n        => range.To.CompareTo(range.From) >= 0;\r\n\r\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\r\n    public static bool IsNormalizedComparer<T>(this Range<T> range, IComparer<T> comparer)\r\n        where T : IComparable<T>\r\n        => comparer.Compare(range.To, range.From) >= 0;\r\n}\r\n```\r\n\r\nTest environment: Intel Xeon E5-2687W, 3100 MHz, 8 cores, 16 threads\r\nIterations: 1000000 for inner loop, 1000000 for outer loop\r\n\r\n| Method                  | Duration (msec) |\r\n|:------------------------|----------------:|\r\n| IsNormalizedSpecialized |             643 |\r\n| IsNormalizedGeneric     |             975 |\r\n| IsNormalizedComparer    |            1336 |\r\n\r\nDuration is measured for a single outer loop iteration.\r\n\r\n## Open Questions\r\n\r\n* Should `Range<T>` support nullable values? That prevents being of `IComparable<T>` constraint on the generic type parameter, but at the same time allows to have open-ended ranges. Type safety could be implemented in the static constructor of `Range<T>`.\r\n\r\n```csharp\r\nstatic Range()\r\n{\r\n    var valueType = typeof(T);\r\n    if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Nullable<>))\r\n    {\r\n        valueType = valueType.GetGenericArguments()[0];\r\n    }\r\n\r\n    var comparableType = typeof(IComparable<>).MakeGenericType(valueType);\r\n    if (!comparableType.IsAssignableFrom(valueType))\r\n    {\r\n        throw new Exception($\"The {valueType.Name} type doesn't implement {typeof(IComparable<>).Name}.\");\r\n    }\r\n}\r\n```\r\n\r\nAnother way to do that is to use `T?` as the field type, but it would increase the size of generic instances and would cause performance lose.\r\n\r\n```csharp\r\npublic readonly struct Range<T> : IEquatable<Range<T>>, IFormattable, ISerializable\r\n    where T : IComparable<T>\r\n{\r\n    public readonly T? From;\r\n    public readonly T? To;\r\n}\r\n\r\npublic static class Range\r\n{\r\n    public static int? Length(this Range<int?> range) { throw null; }\r\n}\r\n```\r\n\r\n| Implementation (`T` is `int`) | Size (bytes) |\r\n|:------------------------------|-------------:|\r\n| `Range(T, T)`                 |            8 |\r\n| `Range(T?, T?)`               |           16 |"},"comment":{"url":"https://api.github.com/repos/dotnet/corefx/issues/comments/344998332","html_url":"https://github.com/dotnet/corefx/issues/25220#issuecomment-344998332","issue_url":"https://api.github.com/repos/dotnet/corefx/issues/25220","id":344998332,"user":{"login":"KrzysztofCwalina","id":9724236,"avatar_url":"https://avatars0.githubusercontent.com/u/9724236?v=4","gravatar_id":"","url":"https://api.github.com/users/KrzysztofCwalina","html_url":"https://github.com/KrzysztofCwalina","followers_url":"https://api.github.com/users/KrzysztofCwalina/followers","following_url":"https://api.github.com/users/KrzysztofCwalina/following{/other_user}","gists_url":"https://api.github.com/users/KrzysztofCwalina/gists{/gist_id}","starred_url":"https://api.github.com/users/KrzysztofCwalina/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KrzysztofCwalina/subscriptions","organizations_url":"https://api.github.com/users/KrzysztofCwalina/orgs","repos_url":"https://api.github.com/users/KrzysztofCwalina/repos","events_url":"https://api.github.com/users/KrzysztofCwalina/events{/privacy}","received_events_url":"https://api.github.com/users/KrzysztofCwalina/received_events","type":"User","site_admin":false},"created_at":"2017-11-16T17:31:36Z","updated_at":"2017-11-16T17:31:36Z","author_association":"MEMBER","body":"The corfxlab Range is not ready yet. There are the following open questions:\r\n1. Should Range be generic?\r\n2. If not, should its indexes be long or int?\r\n3. The compiler team wants to provide syntax for ranges, will this range type worrk for the compiler?\r\n4. Is range (index, count) or (from,to)\r\n5. If (from,to), is it exclusive or inclusive?\r\n6. In Python, ranges/sliceing can support skipping elements. Do we want to support it to?"}}
{"actor":{"display_login":"KrzysztofCwalina"}}