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 a715ea06b7 GH-38692: [C#] Implement ICollection<T?> on scalar arrays 
(#41539)
a715ea06b7 is described below

commit a715ea06b71ec206a987d7921264778e9954404b
Author: Gavin Murrison <[email protected]>
AuthorDate: Mon May 13 16:38:14 2024 +0100

    GH-38692: [C#] Implement ICollection<T?> on scalar arrays (#41539)
    
    ### What changes are included in this PR?
    
    This PR makes the following array types support ICollection<T?> :
    - PrimitiveArray
    - BooleanArray
    - Date32Array
    - Date64Array
    - Time32Array
    - Time64Array
    - BinaryArray
    - TimestampArray
    - StringArray
    
    ### Are these changes tested?
    
    Yes
    
    ### Are there any user-facing changes?
    
    No
    
    Closes #38692
    * GitHub Issue: #38692
    
    Authored-by: voidstar69 <[email protected]>
    Signed-off-by: Curt Hagenlocher <[email protected]>
---
 csharp/src/Apache.Arrow/Arrays/BinaryArray.cs      |  27 +++-
 csharp/src/Apache.Arrow/Arrays/BooleanArray.cs     |  29 ++++-
 csharp/src/Apache.Arrow/Arrays/Date32Array.cs      |  63 ++++++++-
 csharp/src/Apache.Arrow/Arrays/Date64Array.cs      |  63 ++++++++-
 csharp/src/Apache.Arrow/Arrays/IntervalArray.cs    |   2 +-
 csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs   |  37 +++++-
 .../Apache.Arrow/Arrays/PrimitiveArrayBuilder.cs   |   2 +-
 csharp/src/Apache.Arrow/Arrays/StringArray.cs      |  27 +++-
 csharp/src/Apache.Arrow/Arrays/Time32Array.cs      |  27 +++-
 csharp/src/Apache.Arrow/Arrays/Time64Array.cs      |  27 +++-
 csharp/src/Apache.Arrow/Arrays/TimestampArray.cs   |  27 +++-
 csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs   |   2 +-
 .../test/Apache.Arrow.IntegrationTest/JsonFile.cs  |   6 +-
 csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs  | 145 ++++++++++++++++++++-
 csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs |   2 +-
 .../Extensions/DateTimeOffsetExtensions.cs         |   2 -
 csharp/test/Apache.Arrow.Tests/UnionArrayTests.cs  |   2 +-
 17 files changed, 450 insertions(+), 40 deletions(-)

diff --git a/csharp/src/Apache.Arrow/Arrays/BinaryArray.cs 
b/csharp/src/Apache.Arrow/Arrays/BinaryArray.cs
index 1bd4035d5b..0c84fa2be2 100644
--- a/csharp/src/Apache.Arrow/Arrays/BinaryArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/BinaryArray.cs
@@ -22,7 +22,7 @@ using System.Collections;
 
 namespace Apache.Arrow
 {
-    public class BinaryArray : Array, IReadOnlyList<byte[]>
+    public class BinaryArray : Array, IReadOnlyList<byte[]>, 
ICollection<byte[]>
     {
         public class Builder : BuilderBase<BinaryArray, Builder>
         {
@@ -380,5 +380,30 @@ namespace Apache.Arrow
         }
 
         IEnumerator IEnumerable.GetEnumerator() => 
((IEnumerable<byte[]>)this).GetEnumerator();
+
+        int ICollection<byte[]>.Count => Length;
+        bool ICollection<byte[]>.IsReadOnly => true;
+        void ICollection<byte[]>.Add(byte[]? item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<byte[]>.Remove(byte[]? item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<byte[]>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<byte[]>.Contains(byte[] item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetBytes(index).SequenceEqual(item))
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<byte[]>.CopyTo(byte[][] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetBytes(srcIndex).ToArray();
+            }
+        }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/BooleanArray.cs 
b/csharp/src/Apache.Arrow/Arrays/BooleanArray.cs
index e9c5f8979e..19d4d0b7ed 100644
--- a/csharp/src/Apache.Arrow/Arrays/BooleanArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/BooleanArray.cs
@@ -21,7 +21,7 @@ using System.Collections.Generic;
 
 namespace Apache.Arrow
 {
-    public class BooleanArray: Array, IReadOnlyList<bool?>
+    public class BooleanArray: Array, IReadOnlyList<bool?>, ICollection<bool?>
     {
         public class Builder : IArrowArrayBuilder<bool, BooleanArray, Builder>
         {
@@ -188,7 +188,7 @@ namespace Apache.Arrow
         public bool? GetValue(int index)
         {
             return IsNull(index)
-                ? (bool?)null
+                ? null
                 : BitUtility.GetBit(ValueBuffer.Span, index + Offset);
         }
 
@@ -205,5 +205,30 @@ namespace Apache.Arrow
         }
 
         IEnumerator IEnumerable.GetEnumerator() => 
((IEnumerable<bool?>)this).GetEnumerator();
+
+        int ICollection<bool?>.Count => Length;
+        bool ICollection<bool?>.IsReadOnly => true;
+        void ICollection<bool?>.Add(bool? item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<bool?>.Remove(bool? item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<bool?>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<bool?>.Contains(bool? item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetValue(index).Equals(item))
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<bool?>.CopyTo(bool?[] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetValue(srcIndex);
+            }
+        }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/Date32Array.cs 
b/csharp/src/Apache.Arrow/Arrays/Date32Array.cs
index 6ab4986f57..55864e89e2 100644
--- a/csharp/src/Apache.Arrow/Arrays/Date32Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Date32Array.cs
@@ -23,9 +23,9 @@ 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>, IReadOnlyList<DateTime?>
+    public class Date32Array : PrimitiveArray<int>, IReadOnlyList<DateTime?>, 
ICollection<DateTime?>
 #if NET6_0_OR_GREATER
-        , IReadOnlyList<DateOnly?>
+        , IReadOnlyList<DateOnly?>, ICollection<DateOnly?>
 #endif
     {
         private static readonly DateTime _epochDate = new DateTime(1970, 1, 1, 
0, 0, 0, DateTimeKind.Unspecified);
@@ -40,10 +40,9 @@ namespace Apache.Arrow
         {
             private class DateBuilder : PrimitiveArrayBuilder<int, 
Date32Array, DateBuilder>
             {
-                protected override Date32Array Build(
-                    ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
-                    int length, int nullCount, int offset) =>
-                    new Date32Array(valueBuffer, nullBitmapBuffer, length, 
nullCount, offset);
+                protected override Date32Array Build(ArrowBuffer valueBuffer, 
ArrowBuffer nullBitmapBuffer, int length,
+                    int nullCount, int offset) =>
+                    new(valueBuffer, nullBitmapBuffer, length, nullCount, 
offset);
             }
 
             /// <summary>
@@ -149,6 +148,31 @@ namespace Apache.Arrow
                 yield return GetDateOnly(index);
             };
         }
+
+        int ICollection<DateOnly?>.Count => Length;
+        bool ICollection<DateOnly?>.IsReadOnly => true;
+        void ICollection<DateOnly?>.Add(DateOnly? item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<DateOnly?>.Remove(DateOnly? item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<DateOnly?>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<DateOnly?>.Contains(DateOnly? item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetDateOnly(index).Equals(item))
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<DateOnly?>.CopyTo(DateOnly?[] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetDateOnly(srcIndex);
+            }
+        }
 #endif
 
         int IReadOnlyCollection<DateTime?>.Count => Length;
@@ -160,7 +184,32 @@ namespace Apache.Arrow
             for (int index = 0; index < Length; index++)
             {
                 yield return GetDateTime(index);
-            };
+            }
+        }
+
+        int ICollection<DateTime?>.Count => Length;
+        bool ICollection<DateTime?>.IsReadOnly => true;
+        void ICollection<DateTime?>.Add(DateTime? item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<DateTime?>.Remove(DateTime? item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<DateTime?>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<DateTime?>.Contains(DateTime? item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetDateTime(index).Equals(item))
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<DateTime?>.CopyTo(DateTime?[] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetDateTime(srcIndex);
+            }
         }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/Date64Array.cs 
b/csharp/src/Apache.Arrow/Arrays/Date64Array.cs
index 43e698e10b..77538ce59f 100644
--- a/csharp/src/Apache.Arrow/Arrays/Date64Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Date64Array.cs
@@ -24,9 +24,9 @@ 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>, IReadOnlyList<DateTime?>
+    public class Date64Array : PrimitiveArray<long>, IReadOnlyList<DateTime?>, 
ICollection<DateTime?>
 #if NET6_0_OR_GREATER
-        , IReadOnlyList<DateOnly?>
+        , IReadOnlyList<DateOnly?>, ICollection<DateOnly?>
 #endif
     {
         private const long MillisecondsPerDay = 86400000;
@@ -45,10 +45,9 @@ namespace Apache.Arrow
         {
             private class DateBuilder : PrimitiveArrayBuilder<long, 
Date64Array, DateBuilder>
             {
-                protected override Date64Array Build(
-                    ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
-                    int length, int nullCount, int offset) =>
-                    new Date64Array(valueBuffer, nullBitmapBuffer, length, 
nullCount, offset);
+                protected override Date64Array Build(ArrowBuffer valueBuffer, 
ArrowBuffer nullBitmapBuffer, int length,
+                    int nullCount, int offset) =>
+                    new(valueBuffer, nullBitmapBuffer, length, nullCount, 
offset);
             }
 
             /// <summary>
@@ -151,6 +150,31 @@ namespace Apache.Arrow
                 yield return GetDateOnly(index);
             };
         }
+
+        int ICollection<DateOnly?>.Count => Length;
+        bool ICollection<DateOnly?>.IsReadOnly => true;
+        void ICollection<DateOnly?>.Add(DateOnly? item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<DateOnly?>.Remove(DateOnly? item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<DateOnly?>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<DateOnly?>.Contains(DateOnly? item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetDateOnly(index).Equals(item))
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<DateOnly?>.CopyTo(DateOnly?[] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetDateOnly(srcIndex);
+            }
+        }
 #endif
 
         int IReadOnlyCollection<DateTime?>.Count => Length;
@@ -162,7 +186,32 @@ namespace Apache.Arrow
             for (int index = 0; index < Length; index++)
             {
                 yield return GetDateTime(index);
-            };
+            }
+        }
+
+        int ICollection<DateTime?>.Count => Length;
+        bool ICollection<DateTime?>.IsReadOnly => true;
+        void ICollection<DateTime?>.Add(DateTime? item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<DateTime?>.Remove(DateTime? item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<DateTime?>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<DateTime?>.Contains(DateTime? item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetDateTime(index).Equals(item))
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<DateTime?>.CopyTo(DateTime?[] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetDateTime(srcIndex);
+            }
         }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/IntervalArray.cs 
b/csharp/src/Apache.Arrow/Arrays/IntervalArray.cs
index de4fc42b4c..3949af877b 100644
--- a/csharp/src/Apache.Arrow/Arrays/IntervalArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/IntervalArray.cs
@@ -31,7 +31,7 @@ namespace Apache.Arrow
     }
 
     public abstract class IntervalArray<T> : PrimitiveArray<T>
-        where T : struct
+        where T : struct, IEquatable<T>
     {
         protected IntervalArray(ArrayData data)
             : base(data)
diff --git a/csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs 
b/csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs
index 0456c5cc65..05d659b527 100644
--- a/csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/PrimitiveArray.cs
@@ -20,8 +20,8 @@ using System.Runtime.CompilerServices;
 
 namespace Apache.Arrow
 {
-    public abstract class PrimitiveArray<T> : Array, IReadOnlyList<T?>
-        where T : struct
+    public abstract class PrimitiveArray<T> : Array, IReadOnlyList<T?>, 
ICollection<T?>
+        where T : struct, IEquatable<T>
     {
         protected PrimitiveArray(ArrayData data)
             : base(data)
@@ -40,7 +40,7 @@ namespace Apache.Arrow
             {
                 throw new ArgumentOutOfRangeException(nameof(index));
             }
-            return IsValid(index) ? Values[index] : (T?)null;
+            return IsValid(index) ? Values[index] : null;
         }
 
         public IList<T?> ToList(bool includeNulls = false)
@@ -86,5 +86,36 @@ namespace Apache.Arrow
                 yield return IsValid(index) ? Values[index] : null;
             }
         }
+
+        int ICollection<T?>.Count => Length;
+        bool ICollection<T?>.IsReadOnly => true;
+        void ICollection<T?>.Add(T? item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<T?>.Remove(T? item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<T?>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<T?>.Contains(T? item)
+        {
+            if (item == null)
+            {
+                return NullCount > 0;
+            }
+
+            ReadOnlySpan<T> values = Values;
+            while (values.Length > 0)
+            {
+                int index = Values.IndexOf(item.Value);
+                if (index < 0 || IsValid(index)) { return index >= 0; }
+                values = values.Slice(index + 1);
+            }
+            return false;
+        }
+
+        void ICollection<T?>.CopyTo(T?[] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetValue(srcIndex);
+            }
+        }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/PrimitiveArrayBuilder.cs 
b/csharp/src/Apache.Arrow/Arrays/PrimitiveArrayBuilder.cs
index 67fe46633c..ae02173fb0 100644
--- a/csharp/src/Apache.Arrow/Arrays/PrimitiveArrayBuilder.cs
+++ b/csharp/src/Apache.Arrow/Arrays/PrimitiveArrayBuilder.cs
@@ -20,7 +20,7 @@ using System.Linq;
 
 namespace Apache.Arrow
 {
-    public abstract class PrimitiveArrayBuilder<TFrom, TTo, TArray, TBuilder> 
: IArrowArrayBuilder<TArray, TBuilder>
+    public abstract class PrimitiveArrayBuilder<TFrom, TTo, TArray, TBuilder> 
: IArrowArrayBuilder<TFrom, TArray, TBuilder>
         where TTo : struct
         where TArray : IArrowArray
         where TBuilder : class, IArrowArrayBuilder<TArray>
diff --git a/csharp/src/Apache.Arrow/Arrays/StringArray.cs 
b/csharp/src/Apache.Arrow/Arrays/StringArray.cs
index a3ec596adc..ab44805d8d 100644
--- a/csharp/src/Apache.Arrow/Arrays/StringArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/StringArray.cs
@@ -22,7 +22,7 @@ using Apache.Arrow.Types;
 
 namespace Apache.Arrow
 {
-    public class StringArray: BinaryArray, IReadOnlyList<string>
+    public class StringArray: BinaryArray, IReadOnlyList<string>, 
ICollection<string>
     {
         public static readonly Encoding DefaultEncoding = Encoding.UTF8;
 
@@ -164,5 +164,30 @@ namespace Apache.Arrow
         }
 
         IEnumerator IEnumerable.GetEnumerator() => 
((IEnumerable<string>)this).GetEnumerator();
+
+        int ICollection<string>.Count => Length;
+        bool ICollection<string>.IsReadOnly => true;
+        void ICollection<string>.Add(string item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<string>.Remove(string item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<string>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<string>.Contains(string item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetString(index) == item)
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<string>.CopyTo(string[] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetString(srcIndex);
+            }
+        }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/Time32Array.cs 
b/csharp/src/Apache.Arrow/Arrays/Time32Array.cs
index e9c2d7a4d9..63c0898935 100644
--- a/csharp/src/Apache.Arrow/Arrays/Time32Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Time32Array.cs
@@ -26,7 +26,7 @@ namespace Apache.Arrow
     /// </summary>
     public class Time32Array : PrimitiveArray<int>
 #if NET6_0_OR_GREATER
-        , IReadOnlyList<TimeOnly?>
+        , IReadOnlyList<TimeOnly?>, ICollection<TimeOnly?>
 #endif
     {
         /// <summary>
@@ -171,6 +171,31 @@ namespace Apache.Arrow
                 yield return GetTime(index);
             };
         }
+
+        int ICollection<TimeOnly?>.Count => Length;
+        bool ICollection<TimeOnly?>.IsReadOnly => true;
+        void ICollection<TimeOnly?>.Add(TimeOnly? item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<TimeOnly?>.Remove(TimeOnly? item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<TimeOnly?>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<TimeOnly?>.Contains(TimeOnly? item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetTime(index).Equals(item))
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<TimeOnly?>.CopyTo(TimeOnly?[] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetTime(srcIndex);
+            }
+        }
 #endif
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/Time64Array.cs 
b/csharp/src/Apache.Arrow/Arrays/Time64Array.cs
index fc18dfb8bf..5518462952 100644
--- a/csharp/src/Apache.Arrow/Arrays/Time64Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Time64Array.cs
@@ -26,7 +26,7 @@ namespace Apache.Arrow
     /// </summary>
     public class Time64Array : PrimitiveArray<long>
 #if NET6_0_OR_GREATER
-        , IReadOnlyList<TimeOnly?>
+        , IReadOnlyList<TimeOnly?>, ICollection<TimeOnly?>
 #endif
     {
         /// <summary>
@@ -162,6 +162,31 @@ namespace Apache.Arrow
                 yield return GetTime(index);
             };
         }
+
+        int ICollection<TimeOnly?>.Count => Length;
+        bool ICollection<TimeOnly?>.IsReadOnly => true;
+        void ICollection<TimeOnly?>.Add(TimeOnly? item) => throw new 
NotSupportedException("Collection is read-only.");
+        bool ICollection<TimeOnly?>.Remove(TimeOnly? item) => throw new 
NotSupportedException("Collection is read-only.");
+        void ICollection<TimeOnly?>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<TimeOnly?>.Contains(TimeOnly? item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetTime(index).Equals(item))
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<TimeOnly?>.CopyTo(TimeOnly?[] array, int arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetTime(srcIndex);
+            }
+        }
 #endif
     }
 }
diff --git a/csharp/src/Apache.Arrow/Arrays/TimestampArray.cs 
b/csharp/src/Apache.Arrow/Arrays/TimestampArray.cs
index ccb656854a..b838605847 100644
--- a/csharp/src/Apache.Arrow/Arrays/TimestampArray.cs
+++ b/csharp/src/Apache.Arrow/Arrays/TimestampArray.cs
@@ -21,7 +21,7 @@ using System.IO;
 
 namespace Apache.Arrow
 {
-    public class TimestampArray : PrimitiveArray<long>, 
IReadOnlyList<DateTimeOffset?>
+    public class TimestampArray : PrimitiveArray<long>, 
IReadOnlyList<DateTimeOffset?>, ICollection<DateTimeOffset?>
     {
         private static readonly DateTimeOffset s_epoch = new 
DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero);
 
@@ -157,5 +157,30 @@ namespace Apache.Arrow
                 yield return GetTimestamp(index);
             };
         }
+
+        int ICollection<DateTimeOffset?>.Count => Length;
+        bool ICollection<DateTimeOffset?>.IsReadOnly => true;
+        void ICollection<DateTimeOffset?>.Add(DateTimeOffset? item) => throw 
new NotSupportedException("Collection is read-only.");
+        bool ICollection<DateTimeOffset?>.Remove(DateTimeOffset? item) => 
throw new NotSupportedException("Collection is read-only.");
+        void ICollection<DateTimeOffset?>.Clear() => throw new 
NotSupportedException("Collection is read-only.");
+
+        bool ICollection<DateTimeOffset?>.Contains(DateTimeOffset? item)
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                if (GetTimestamp(index).Equals(item))
+                    return true;
+            }
+
+            return false;
+        }
+
+        void ICollection<DateTimeOffset?>.CopyTo(DateTimeOffset?[] array, int 
arrayIndex)
+        {
+            for (int srcIndex = 0, destIndex = arrayIndex; srcIndex < Length; 
srcIndex++, destIndex++)
+            {
+                array[destIndex] = GetTimestamp(srcIndex);
+            }
+        }
     }
 }
diff --git a/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs 
b/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
index b11479c0d4..c66569afeb 100644
--- a/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
+++ b/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
@@ -148,7 +148,7 @@ namespace Apache.Arrow.Ipc
             public void Visit(MonthDayNanosecondIntervalArray array) => 
VisitPrimitiveArray(array);
 
             private void VisitPrimitiveArray<T>(PrimitiveArray<T> array)
-                where T : struct
+                where T : struct, IEquatable<T>
             {
                 _buffers.Add(CreateBitmapBuffer(array.NullBitmapBuffer, 
array.Offset, array.Length));
                 _buffers.Add(CreateSlicedBuffer<T>(array.ValueBuffer, 
array.Offset, array.Length));
diff --git a/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs 
b/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs
index 31a5676f01..7232f74b8b 100644
--- a/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs
+++ b/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs
@@ -908,8 +908,8 @@ namespace Apache.Arrow.IntegrationTest
             };
 
             private void GenerateArray<T, TArray>(Func<ArrowBuffer, 
ArrowBuffer, int, int, int, TArray> createArray)
+                where T : struct, IEquatable<T>
                 where TArray : PrimitiveArray<T>
-                where T : struct
             {
                 ArrowBuffer validityBuffer = GetValidityBuffer(out int 
nullCount);
 
@@ -929,8 +929,8 @@ namespace Apache.Arrow.IntegrationTest
             }
 
             private void GenerateLongArray<T, TArray>(Func<ArrowBuffer, 
ArrowBuffer, int, int, int, TArray> createArray, Func<string, T> parse)
+                where T : struct, IEquatable<T>
                 where TArray : PrimitiveArray<T>
-                where T : struct
             {
                 ArrowBuffer validityBuffer = GetValidityBuffer(out int 
nullCount);
 
@@ -950,8 +950,8 @@ namespace Apache.Arrow.IntegrationTest
             }
 
             private void GenerateArray<T, TArray>(Func<ArrowBuffer, 
ArrowBuffer, int, int, int, TArray> createArray, Func<JsonElement, T> construct)
+                where T : struct, IEquatable<T>
                 where TArray : PrimitiveArray<T>
-                where T : struct
             {
                 ArrowBuffer validityBuffer = GetValidityBuffer(out int 
nullCount);
 
diff --git a/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs 
b/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
index 682ebec323..d3032b8d4a 100644
--- a/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
@@ -101,9 +101,9 @@ namespace Apache.Arrow.Tests
         {
             var array = new Int64Array.Builder().Append(1).Append(2).Build();
 
-            foreach(long? foo in (IEnumerable<long?>)array)
+            foreach(long? foo in array)
             {
-                Assert.InRange(foo.Value, 1, 2);
+                Assert.InRange(foo!.Value, 1, 2);
             }
 
             foreach (object foo in (IEnumerable)array)
@@ -115,12 +115,145 @@ namespace Apache.Arrow.Tests
         [Fact]
         public void ArrayAsReadOnlyList()
         {
-            Int64Array array = new 
Int64Array.Builder().Append(1).Append(2).Build();
-            var readOnlyList = (IReadOnlyList<long?>)array;
+            TestArrayAsReadOnlyList<long, Int64Array, Int64Array.Builder>([1, 
2]);
+            TestArrayAsReadOnlyList<byte, UInt8Array, UInt8Array.Builder>([1, 
2]);
+            TestArrayAsReadOnlyList<bool, BooleanArray, 
BooleanArray.Builder>([true, false]);
+            TestArrayAsReadOnlyList<DateTime, Date32Array, 
Date32Array.Builder>([DateTime.MinValue.Date, DateTime.MaxValue.Date]);
+            TestArrayAsReadOnlyList<DateTime, Date64Array, 
Date64Array.Builder>([DateTime.MinValue.Date, DateTime.MaxValue.Date]);
+            TestArrayAsReadOnlyList<DateTimeOffset, TimestampArray, 
TimestampArray.Builder>([DateTimeOffset.MinValue, 
DateTimeOffset.MinValue.AddYears(100)]);
+
+#if NET5_0_OR_GREATER
+            TestArrayAsReadOnlyList<DateOnly, Date32Array, 
Date32Array.Builder>([DateOnly.MinValue, DateOnly.MaxValue]);
+            TestArrayAsReadOnlyList<DateOnly, Date64Array, 
Date64Array.Builder>([DateOnly.MinValue, DateOnly.MaxValue]);
+            TestArrayAsReadOnlyList<TimeOnly, Time32Array, 
Time32Array.Builder>([TimeOnly.MinValue, TimeOnly.MinValue.AddHours(23)]);
+            TestArrayAsReadOnlyList<TimeOnly, Time64Array, 
Time64Array.Builder>([TimeOnly.MinValue, TimeOnly.MaxValue]);
+            TestArrayAsReadOnlyList<Half, HalfFloatArray, 
HalfFloatArray.Builder>([(Half)1.1, (Half)2.2f]);
+#endif
+        }
+
+        // Parameter 'values' must contain two distinct values
+        private static void TestArrayAsReadOnlyList<T, TArray, 
TArrayBuilder>(IReadOnlyList<T> values)
+            where T : struct
+            where TArray : IArrowArray
+            where TArrayBuilder : IArrowArrayBuilder<T, TArray, 
TArrayBuilder>, new()
+        {
+            Assert.Equal(2, values.Count);
+            TArray array = new 
TArrayBuilder().Append(values[0]).AppendNull().Append(values[1]).Build(default);
+            Assert.NotNull(array);
+            var readOnlyList = (IReadOnlyList<T?>)array;
 
             Assert.Equal(array.Length, readOnlyList.Count);
-            Assert.Equal(readOnlyList[0], 1);
-            Assert.Equal(readOnlyList[1], 2);
+            Assert.Equal(3, readOnlyList.Count);
+            Assert.Equal(values[0], readOnlyList[0]);
+            Assert.Null(readOnlyList[1]);
+            Assert.Equal(values[1], readOnlyList[2]);
+        }
+
+        [Fact]
+        public void ArrayAsCollection()
+        {
+            TestPrimitiveArrayAsCollection<long, Int64Array, 
Int64Array.Builder>([1, 2, 3, 4]);
+            TestPrimitiveArrayAsCollection<byte, UInt8Array, 
UInt8Array.Builder>([1, 2, 3, 4]);
+            TestPrimitiveArrayAsCollection<bool, BooleanArray, 
BooleanArray.Builder>([true, true, true, false]);
+            TestPrimitiveArrayAsCollection<DateTime, Date32Array, 
Date32Array.Builder>([DateTime.MinValue.Date, DateTime.MaxValue.Date, 
DateTime.Today, DateTime.Today]);
+            TestPrimitiveArrayAsCollection<DateTime, Date64Array, 
Date64Array.Builder>([DateTime.MinValue.Date, DateTime.MaxValue.Date, 
DateTime.Today, DateTime.Today]);
+            TestPrimitiveArrayAsCollection<DateTimeOffset, TimestampArray, 
TimestampArray.Builder>([DateTimeOffset.MinValue, 
DateTimeOffset.MinValue.AddYears(100), DateTimeOffset.Now, 
DateTimeOffset.UtcNow]);
+
+#if NET5_0_OR_GREATER
+            TestPrimitiveArrayAsCollection<DateOnly, Date32Array, 
Date32Array.Builder>([DateOnly.MinValue, DateOnly.MaxValue, 
DateOnly.FromDayNumber(1), DateOnly.FromDayNumber(2)]);
+            TestPrimitiveArrayAsCollection<DateOnly, Date64Array, 
Date64Array.Builder>([DateOnly.MinValue, DateOnly.MaxValue, 
DateOnly.FromDayNumber(1), DateOnly.FromDayNumber(2)]);
+            TestPrimitiveArrayAsCollection<TimeOnly, Time32Array, 
Time32Array.Builder>([TimeOnly.MinValue, TimeOnly.MinValue.AddHours(23), 
TimeOnly.MinValue.AddHours(1), TimeOnly.MinValue.AddHours(2)]);
+            TestPrimitiveArrayAsCollection<TimeOnly, Time64Array, 
Time64Array.Builder>([TimeOnly.MinValue, TimeOnly.MaxValue, 
TimeOnly.MinValue.AddHours(1), TimeOnly.MinValue.AddHours(2)]);
+            TestPrimitiveArrayAsCollection<Half, HalfFloatArray, 
HalfFloatArray.Builder>([(Half)1.1, (Half)2.2f, (Half)3.3f, (Half)4.4f]);
+#endif
+
+            byte[][] byteArrs = [new byte[1], [], [255], new byte[2]];
+            TestObjectArrayAsCollection(new 
BinaryArray.Builder().Append(byteArrs[0].AsEnumerable()).AppendNull().Append(byteArrs[1].AsEnumerable()).Append(byteArrs[0].AsEnumerable()).Build(),
 System.Array.Empty<byte>(), byteArrs);
+
+            string[] strings = ["abc", "abd", "acd", "adc"];
+            TestObjectArrayAsCollection(new 
StringArray.Builder().Append(strings[0]).AppendNull().Append(strings[1]).Append(strings[0]).Build(),
 null, strings);
+        }
+
+        // Parameter 'values' must contain four values. The last value must be 
distinct from the rest.
+        private static void TestPrimitiveArrayAsCollection<T, TArray, 
TArrayBuilder>(IReadOnlyList<T> values)
+            where T : struct
+            where TArray : IArrowArray, ICollection<T?>
+            where TArrayBuilder : IArrowArrayBuilder<T, TArray, 
TArrayBuilder>, new()
+        {
+            Assert.Equal(4, values.Count);
+            TArray array = new 
TArrayBuilder().Append(values[0]).AppendNull().Append(values[1]).Append(values[0]).Build(default);
+            Assert.NotNull(array);
+            var collection = (ICollection<T?>)array;
+
+            Assert.Equal(array.Length, collection.Count);
+            Assert.Equal(4, collection.Count);
+            Assert.True(collection.IsReadOnly);
+
+            Assert.Equal("Collection is read-only.", 
Assert.Throws<NotSupportedException>(() => collection.Add(values[3])).Message);
+            Assert.Equal("Collection is read-only.", 
Assert.Throws<NotSupportedException>(() => 
collection.Remove(values[3])).Message);
+            Assert.Equal("Collection is read-only.", 
Assert.Throws<NotSupportedException>(collection.Clear).Message);
+
+            Assert.True(collection.Contains(values[0]));
+            Assert.True(collection.Contains(values[1]));
+            Assert.True(collection.Contains(default));
+            Assert.False(collection.Contains(values[3]));
+
+            T sentinel = values[2];
+            T?[] destArr = { sentinel, sentinel, sentinel, sentinel, sentinel, 
sentinel };
+            collection.CopyTo(destArr, 1);
+            Assert.Equal(sentinel, destArr[0]);
+            Assert.Equal(values[0], destArr[1]);
+            Assert.Null(destArr[2]);
+            Assert.Equal(values[1], destArr[3]);
+            Assert.Equal(values[0], destArr[4]);
+            Assert.Equal(sentinel, destArr[0]);
+        }
+
+        // Parameter 'values' must contain four values. The last value must be 
distinct from the rest.
+        private static void TestObjectArrayAsCollection<T, TArray>(TArray 
array, T nullValue, IReadOnlyList<T> values)
+            where T : class
+            where TArray : IArrowArray, ICollection<T?>
+        {
+            Assert.NotNull(array);
+            Assert.Equal(4, values.Count);
+            var collection = (ICollection<T?>)array;
+
+            Assert.Equal(array.Length, collection.Count);
+            Assert.Equal(4, collection.Count);
+            Assert.True(collection.IsReadOnly);
+
+            Assert.Equal("Collection is read-only.", 
Assert.Throws<NotSupportedException>(() => collection.Add(values[3])).Message);
+            Assert.Equal("Collection is read-only.", 
Assert.Throws<NotSupportedException>(() => 
collection.Remove(values[3])).Message);
+            Assert.Equal("Collection is read-only.", 
Assert.Throws<NotSupportedException>(collection.Clear).Message);
+
+            Assert.True(collection.Contains(values[0]));
+            Assert.True(collection.Contains(values[1]));
+            Assert.True(collection.Contains(default));
+            Assert.False(collection.Contains(values[3]));
+
+            T sentinel = values[2];
+            T?[] destArr = { sentinel, sentinel, sentinel, sentinel, sentinel, 
sentinel };
+            collection.CopyTo(destArr, 1);
+            Assert.Equal(sentinel, destArr[0]);
+            Assert.Equal(values[0], destArr[1]);
+            Assert.Equal(nullValue, destArr[2]);
+            Assert.Equal(values[1], destArr[3]);
+            Assert.Equal(values[0], destArr[4]);
+            Assert.Equal(sentinel, destArr[0]);
+        }
+
+        [Fact]
+        public void ContainsDoesNotMatchDefaultValueInArrayWithNullValue()
+        {
+            Int64Array array = new 
Int64Array.Builder().Append(1).Append(2).AppendNull().Build();
+            Assert.NotNull(array);
+            var collection = (ICollection<long?>)array;
+
+            Assert.True(collection.Contains(1));
+            Assert.True(collection.Contains(2));
+            Assert.True(collection.Contains(default));
+            // A null value is stored as a null bit in the null bitmap, and a 
default value in the value buffer. Check that we do not match the default value.
+            Assert.False(collection.Contains(0));
         }
 
         [Fact]
diff --git a/csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs 
b/csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs
index 2a674b942c..6e4742cad0 100644
--- a/csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs
@@ -131,7 +131,7 @@ namespace Apache.Arrow.Tests
         public class AppendDateOnly
         {
             [Theory]
-            [MemberData(nameof(GetDateOnlyData), MemberType = 
typeof(Date64ArrayTests))]
+            [MemberData(nameof(GetDateOnlyData), MemberType = 
typeof(Date32ArrayTests))]
             public void AppendDateGivesSameDate(DateOnly date)
             {
                 // Arrange
diff --git 
a/csharp/test/Apache.Arrow.Tests/Extensions/DateTimeOffsetExtensions.cs 
b/csharp/test/Apache.Arrow.Tests/Extensions/DateTimeOffsetExtensions.cs
index 4375c39cdf..01809735d1 100644
--- a/csharp/test/Apache.Arrow.Tests/Extensions/DateTimeOffsetExtensions.cs
+++ b/csharp/test/Apache.Arrow.Tests/Extensions/DateTimeOffsetExtensions.cs
@@ -14,8 +14,6 @@
 // limitations under the License.
 
 using System;
-using System.Collections.Generic;
-using System.Text;
 
 namespace Apache.Arrow.Tests
 {
diff --git a/csharp/test/Apache.Arrow.Tests/UnionArrayTests.cs 
b/csharp/test/Apache.Arrow.Tests/UnionArrayTests.cs
index 712a87a252..c603ef63a4 100644
--- a/csharp/test/Apache.Arrow.Tests/UnionArrayTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/UnionArrayTests.cs
@@ -110,7 +110,7 @@ public class UnionArrayTests
     }
 
     private static void CompareFieldValue<T, TArray>(byte typeId, UnionArray 
originalArray, int originalIndex, UnionArray slicedArray, int sliceIndex)
-        where T: struct
+        where T : struct, IEquatable<T>
         where TArray : PrimitiveArray<T>
     {
         if (originalArray is DenseUnionArray denseOriginalArray)


Reply via email to