You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by cu...@apache.org on 2023/11/19 17:01:12 UTC

(arrow) branch main updated: GH-38348: [C#] Make PrimitiveArray support IReadOnlyList (#38680)

This is an automated email from the ASF dual-hosted git repository.

curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 96e62d86ab GH-38348: [C#] Make PrimitiveArray<T> support IReadOnlyList<T?> (#38680)
96e62d86ab is described below

commit 96e62d86abe8a39a7fc20af7236bd43a9fd7f2da
Author: Gavin Murrison <21...@users.noreply.github.com>
AuthorDate: Sun Nov 19 17:01:05 2023 +0000

    GH-38348: [C#] Make PrimitiveArray<T> support IReadOnlyList<T?> (#38680)
    
    ### What changes are included in this PR?
    
    Make Arrow arrays of scalar type T implement the same semantic contract as IReadOnlyList<T?>.
    Note that this PR does not include similar support for ICollection<T?>. I could add that support in this PR or a future PR.
    
    ### Are these changes tested?
    
    This PR includes unit tests of the implemented IReadOnlyList<T?> methods.
    * Closes: #38348
    
    Authored-by: voidstar69 <vo...@gmail.com>
    Signed-off-by: Curt Hagenlocher <cu...@hagenlocher.org>
---
 csharp/src/Apache.Arrow/Arrays/BinaryArray.cs     | 16 +++++++++++-
 csharp/src/Apache.Arrow/Arrays/BooleanArray.cs    | 17 +++++++++++-
 csharp/src/Apache.Arrow/Arrays/Date32Array.cs     | 30 ++++++++++++++++++++-
 csharp/src/Apache.Arrow/Arrays/Date64Array.cs     | 32 +++++++++++++++++++++--
 csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs  | 22 +++++++++++++++-
 csharp/src/Apache.Arrow/Arrays/StringArray.cs     | 17 +++++++++++-
 csharp/src/Apache.Arrow/Arrays/Time32Array.cs     | 16 ++++++++++++
 csharp/src/Apache.Arrow/Arrays/Time64Array.cs     | 16 ++++++++++++
 csharp/src/Apache.Arrow/Arrays/TimestampArray.cs  | 14 +++++++++-
 csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs | 29 ++++++++++++++++++++
 10 files changed, 201 insertions(+), 8 deletions(-)

diff --git a/csharp/src/Apache.Arrow/Arrays/BinaryArray.cs b/csharp/src/Apache.Arrow/Arrays/BinaryArray.cs
index a7ddb14af6..1bd4035d5b 100644
--- a/csharp/src/Apache.Arrow/Arrays/BinaryArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/BinaryArray.cs
@@ -18,10 +18,11 @@ using System;
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
 using Apache.Arrow.Memory;
+using System.Collections;
 
 namespace Apache.Arrow
 {
-    public class BinaryArray : Array
+    public class BinaryArray : Array, IReadOnlyList<byte[]>
     {
         public class Builder : BuilderBase<BinaryArray, Builder>
         {
@@ -366,5 +367,18 @@ namespace Apache.Arrow
 
             return ValueBuffer.Span.Slice(ValueOffsets[index], GetValueLength(index));
         }
+
+        int IReadOnlyCollection<byte[]>.Count => Length;
+        byte[] IReadOnlyList<byte[]>.this[int index] => GetBytes(index).ToArray();
+
+        IEnumerator<byte[]> IEnumerable<byte[]>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetBytes(index).ToArray();
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<byte[]>)this).GetEnumerator();
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/BooleanArray.cs b/csharp/src/Apache.Arrow/Arrays/BooleanArray.cs
index 0915338fe6..e9c5f8979e 100644
--- a/csharp/src/Apache.Arrow/Arrays/BooleanArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/BooleanArray.cs
@@ -16,11 +16,12 @@
 using Apache.Arrow.Memory;
 using Apache.Arrow.Types;
 using System;
+using System.Collections;
 using System.Collections.Generic;
 
 namespace Apache.Arrow
 {
-    public class BooleanArray: Array
+    public class BooleanArray: Array, IReadOnlyList<bool?>
     {
         public class Builder : IArrowArrayBuilder<bool, BooleanArray, Builder>
         {
@@ -190,5 +191,19 @@ namespace Apache.Arrow
                 ? (bool?)null
                 : BitUtility.GetBit(ValueBuffer.Span, index + Offset);
         }
+
+        int IReadOnlyCollection<bool?>.Count => Length;
+
+        bool? IReadOnlyList<bool?>.this[int index] => GetValue(index);
+
+        IEnumerator<bool?> IEnumerable<bool?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetValue(index);
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<bool?>)this).GetEnumerator();
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/Date32Array.cs b/csharp/src/Apache.Arrow/Arrays/Date32Array.cs
index 23ad7356eb..6ab4986f57 100644
--- a/csharp/src/Apache.Arrow/Arrays/Date32Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Date32Array.cs
@@ -15,6 +15,7 @@
 
 using Apache.Arrow.Types;
 using System;
+using System.Collections.Generic;
 
 namespace Apache.Arrow
 {
@@ -22,7 +23,10 @@ namespace Apache.Arrow
     /// The <see cref="Date32Array"/> class holds an array of dates in the <c>Date32</c> format, where each date is
     /// stored as the number of days since the dawn of (UNIX) time.
     /// </summary>
-    public class Date32Array : PrimitiveArray<int>
+    public class Date32Array : PrimitiveArray<int>, IReadOnlyList<DateTime?>
+#if NET6_0_OR_GREATER
+        , IReadOnlyList<DateOnly?>
+#endif
     {
         private static readonly DateTime _epochDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
 #if NET6_0_OR_GREATER
@@ -133,6 +137,30 @@ namespace Apache.Arrow
                 ? DateOnly.FromDayNumber(_epochDayNumber + value.Value)
                 : default(DateOnly?);
         }
+
+        int IReadOnlyCollection<DateOnly?>.Count => Length;
+
+        DateOnly? IReadOnlyList<DateOnly?>.this[int index] => GetDateOnly(index);
+
+        IEnumerator<DateOnly?> IEnumerable<DateOnly?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetDateOnly(index);
+            };
+        }
 #endif
+
+        int IReadOnlyCollection<DateTime?>.Count => Length;
+
+        DateTime? IReadOnlyList<DateTime?>.this[int index] => GetDateTime(index);
+
+        IEnumerator<DateTime?> IEnumerable<DateTime?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetDateTime(index);
+            };
+        }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/Date64Array.cs b/csharp/src/Apache.Arrow/Arrays/Date64Array.cs
index b0d42e27bb..43e698e10b 100644
--- a/csharp/src/Apache.Arrow/Arrays/Date64Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Date64Array.cs
@@ -15,6 +15,7 @@
 
 using Apache.Arrow.Types;
 using System;
+using System.Collections.Generic;
 
 namespace Apache.Arrow
 {
@@ -23,7 +24,10 @@ namespace Apache.Arrow
     /// stored as the number of milliseconds since the dawn of (UNIX) time, excluding leap seconds, in multiples of
     /// 86400000.
     /// </summary>
-    public class Date64Array: PrimitiveArray<long>
+    public class Date64Array : PrimitiveArray<long>, IReadOnlyList<DateTime?>
+#if NET6_0_OR_GREATER
+        , IReadOnlyList<DateOnly?>
+#endif
     {
         private const long MillisecondsPerDay = 86400000;
 
@@ -39,7 +43,7 @@ namespace Apache.Arrow
         /// </summary>
         public class Builder : DateArrayBuilder<long, Date64Array, Builder>
         {
-            private class DateBuilder: PrimitiveArrayBuilder<long, Date64Array, DateBuilder>
+            private class DateBuilder : PrimitiveArrayBuilder<long, Date64Array, DateBuilder>
             {
                 protected override Date64Array Build(
                     ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
@@ -135,6 +139,30 @@ namespace Apache.Arrow
                 ? DateOnly.FromDateTime(DateTimeOffset.FromUnixTimeMilliseconds(value.Value).UtcDateTime)
                 : default(DateOnly?);
         }
+
+        int IReadOnlyCollection<DateOnly?>.Count => Length;
+
+        DateOnly? IReadOnlyList<DateOnly?>.this[int index] => GetDateOnly(index);
+
+        IEnumerator<DateOnly?> IEnumerable<DateOnly?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetDateOnly(index);
+            };
+        }
 #endif
+
+        int IReadOnlyCollection<DateTime?>.Count => Length;
+
+        DateTime? IReadOnlyList<DateTime?>.this[int index] => GetDateTime(index);
+
+        IEnumerator<DateTime?> IEnumerable<DateTime?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetDateTime(index);
+            };
+        }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs b/csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs
index 7365a77b63..0456c5cc65 100644
--- a/csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs
@@ -14,12 +14,13 @@
 // limitations under the License.
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
 
 namespace Apache.Arrow
 {
-    public abstract class PrimitiveArray<T> : Array
+    public abstract class PrimitiveArray<T> : Array, IReadOnlyList<T?>
         where T : struct
     {
         protected PrimitiveArray(ArrayData data)
@@ -66,5 +67,24 @@ namespace Apache.Arrow
 
             return list;
         }
+
+        int IReadOnlyCollection<T?>.Count => Length;
+        T? IReadOnlyList<T?>.this[int index] => GetValue(index);
+
+        IEnumerator<T?> IEnumerable<T?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return IsValid(index) ? Values[index] : null;
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return IsValid(index) ? Values[index] : null;
+            }
+        }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/StringArray.cs b/csharp/src/Apache.Arrow/Arrays/StringArray.cs
index 42104b2717..af77fe1b1a 100644
--- a/csharp/src/Apache.Arrow/Arrays/StringArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/StringArray.cs
@@ -15,13 +15,14 @@
 
 using Apache.Arrow.Types;
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using System.Text;
 
 namespace Apache.Arrow
 {
-    public class StringArray: BinaryArray
+    public class StringArray: BinaryArray, IReadOnlyList<string>
     {
         public static readonly Encoding DefaultEncoding = Encoding.UTF8;
 
@@ -91,5 +92,19 @@ namespace Apache.Arrow
                     return encoding.GetString(data, bytes.Length);
             }
         }
+
+        int IReadOnlyCollection<string>.Count => Length;
+
+        string IReadOnlyList<string>.this[int index] => GetString(index);
+
+        IEnumerator<string> IEnumerable<string>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetString(index);
+            };
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<string>)this).GetEnumerator();
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/Time32Array.cs b/csharp/src/Apache.Arrow/Arrays/Time32Array.cs
index 824694cd6d..e9c2d7a4d9 100644
--- a/csharp/src/Apache.Arrow/Arrays/Time32Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Time32Array.cs
@@ -15,6 +15,7 @@
 
 using Apache.Arrow.Types;
 using System;
+using System.Collections.Generic;
 using System.IO;
 
 namespace Apache.Arrow
@@ -24,6 +25,9 @@ namespace Apache.Arrow
     /// stored as the number of seconds/ milliseconds (depending on the Time32Type) since midnight.
     /// </summary>
     public class Time32Array : PrimitiveArray<int>
+#if NET6_0_OR_GREATER
+        , IReadOnlyList<TimeOnly?>
+#endif
     {
         /// <summary>
         /// The <see cref="Builder"/> class can be used to fluently build <see cref="Time32Array"/> objects.
@@ -155,6 +159,18 @@ namespace Apache.Arrow
                 _ => throw new InvalidDataException($"Unsupported time unit for Time32Type: {unit}")
             };
         }
+
+        int IReadOnlyCollection<TimeOnly?>.Count => Length;
+
+        TimeOnly? IReadOnlyList<TimeOnly?>.this[int index] => GetTime(index);
+
+        IEnumerator<TimeOnly?> IEnumerable<TimeOnly?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetTime(index);
+            };
+        }
 #endif
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/Time64Array.cs b/csharp/src/Apache.Arrow/Arrays/Time64Array.cs
index 3369893304..fc18dfb8bf 100644
--- a/csharp/src/Apache.Arrow/Arrays/Time64Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Time64Array.cs
@@ -15,6 +15,7 @@
 
 using Apache.Arrow.Types;
 using System;
+using System.Collections.Generic;
 using System.IO;
 
 namespace Apache.Arrow
@@ -24,6 +25,9 @@ namespace Apache.Arrow
     /// stored as the number of microseconds/nanoseconds (depending on the Time64Type) since midnight.
     /// </summary>
     public class Time64Array : PrimitiveArray<long>
+#if NET6_0_OR_GREATER
+        , IReadOnlyList<TimeOnly?>
+#endif
     {
         /// <summary>
         /// The <see cref="Builder"/> class can be used to fluently build <see cref="Time64Array"/> objects.
@@ -146,6 +150,18 @@ namespace Apache.Arrow
 
             return new TimeOnly(((Time64Type)Data.DataType).Unit.ConvertToTicks(value.Value));
         }
+
+        int IReadOnlyCollection<TimeOnly?>.Count => Length;
+
+        TimeOnly? IReadOnlyList<TimeOnly?>.this[int index] => GetTime(index);
+
+        IEnumerator<TimeOnly?> IEnumerable<TimeOnly?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetTime(index);
+            };
+        }
 #endif
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/TimestampArray.cs b/csharp/src/Apache.Arrow/Arrays/TimestampArray.cs
index 0dc5726d01..ccb656854a 100644
--- a/csharp/src/Apache.Arrow/Arrays/TimestampArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/TimestampArray.cs
@@ -15,12 +15,13 @@
 
 using Apache.Arrow.Types;
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 
 namespace Apache.Arrow
 {
-    public class TimestampArray: PrimitiveArray<long>
+    public class TimestampArray : PrimitiveArray<long>, IReadOnlyList<DateTimeOffset?>
     {
         private static readonly DateTimeOffset s_epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero);
 
@@ -145,5 +146,16 @@ namespace Apache.Arrow
             return GetTimestampUnchecked(index);
         }
 
+        int IReadOnlyCollection<DateTimeOffset?>.Count => Length;
+
+        DateTimeOffset? IReadOnlyList<DateTimeOffset?>.this[int index] => GetTimestamp(index);
+
+        IEnumerator<DateTimeOffset?> IEnumerable<DateTimeOffset?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetTimestamp(index);
+            };
+        }
     }
 }
diff --git a/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs b/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
index 96918ff091..269c2390a7 100644
--- a/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
@@ -14,6 +14,8 @@
 // limitations under the License.
 
 using System;
+using System.Collections;
+using System.Collections.Generic;
 using System.Numerics;
 using Xunit;
 
@@ -93,6 +95,33 @@ namespace Apache.Arrow.Tests
             }
         }
 
+        [Fact]
+        public void EnumerateArray()
+        {
+            var array = new Int64Array.Builder().Append(1).Append(2).Build();
+
+            foreach(long? foo in (IEnumerable<long?>)array)
+            {
+                Assert.InRange(foo.Value, 1, 2);
+            }
+
+            foreach (object foo in (IEnumerable)array)
+            {
+                Assert.InRange((long)foo, 1, 2);
+            }
+        }
+
+        [Fact]
+        public void ArrayAsReadOnlyList()
+        {
+            Int64Array array = new Int64Array.Builder().Append(1).Append(2).Build();
+            var readOnlyList = (IReadOnlyList<long?>)array;
+
+            Assert.Equal(array.Length, readOnlyList.Count);
+            Assert.Equal(readOnlyList[0], 1);
+            Assert.Equal(readOnlyList[1], 2);
+        }
+
 #if NET5_0_OR_GREATER
         [Fact]
         public void SliceArray()