You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by bl...@apache.org on 2019/11/05 01:36:02 UTC
[avro] branch master updated: AVRO-2606: Fix C# multidimensional
array errors (#699)
This is an automated email from the ASF dual-hosted git repository.
blachniet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git
The following commit(s) were added to refs/heads/master by this push:
new 62bdc83 AVRO-2606: Fix C# multidimensional array errors (#699)
62bdc83 is described below
commit 62bdc83e46fbd65d34a1715c1bcc53fa5d1919fa
Author: Brian Lachniet <bl...@gmail.com>
AuthorDate: Mon Nov 4 20:35:54 2019 -0500
AVRO-2606: Fix C# multidimensional array errors (#699)
Fix errors surrounding the use of multidimensional arrays of custom
record types in the C# specific API.
---
.../src/apache/main/Specific/ObjectCreator.cs | 71 +++++++++-
.../test/Specific/EmbeddedGenericRecordUser.cs | 73 ++++++++++
.../apache/test/Specific/EmbeddedGenericsRecord.cs | 54 +++++++-
.../src/apache/test/Specific/ObjectCreatorTests.cs | 34 +++--
.../src/apache/test/Specific/SpecificTests.cs | 148 +++++++++++++++++++--
5 files changed, 344 insertions(+), 36 deletions(-)
diff --git a/lang/csharp/src/apache/main/Specific/ObjectCreator.cs b/lang/csharp/src/apache/main/Specific/ObjectCreator.cs
index b0cf9ef..950d111 100644
--- a/lang/csharp/src/apache/main/Specific/ObjectCreator.cs
+++ b/lang/csharp/src/apache/main/Specific/ObjectCreator.cs
@@ -44,6 +44,11 @@ namespace Avro.Specific
private readonly Type GenericListType = typeof(List<>);
/// <summary>
+ /// Static generic list type used for creating new IList instances
+ /// </summary>
+ private readonly Type GenericIListType = typeof(IList<>);
+
+ /// <summary>
/// Static generic nullable type used for creating new nullable instances
/// </summary>
private readonly Type GenericNullableType = typeof(Nullable<>);
@@ -136,11 +141,15 @@ namespace Avro.Specific
{
Type type = null;
- // Modify provided type to ensure it can be discovered.
- // This is mainly for Generics, and Nullables.
- name = name.Replace("Nullable<", "Nullable`1[");
- name = name.Replace("IList<", "System.Collections.Generic.IList`1[");
- name = name.Replace(">", "]");
+ if (TryGetIListItemTypeName(name, out var itemTypeName))
+ {
+ return GenericIListType.MakeGenericType(FindType(itemTypeName));
+ }
+
+ if (TryGetNullableItemTypeName(name, out itemTypeName))
+ {
+ return GenericNullableType.MakeGenericType(FindType(itemTypeName));
+ }
// if entry assembly different from current assembly, try entry assembly first
if (diffAssembly)
@@ -186,6 +195,58 @@ namespace Avro.Specific
});
}
+ private bool TryGetIListItemTypeName(string name, out string itemTypeName)
+ {
+ const string listPrefix = "IList<";
+ const string fullListPrefix = "System.Collections.Generic.IList<";
+
+ if (!name.EndsWith(">", StringComparison.Ordinal))
+ {
+ itemTypeName = null;
+ return false;
+ }
+
+ if (name.StartsWith(fullListPrefix, StringComparison.Ordinal))
+ {
+ itemTypeName = name.Substring(
+ fullListPrefix.Length, name.Length - fullListPrefix.Length - 1);
+ return true;
+ }
+
+ if (name.StartsWith(listPrefix, StringComparison.Ordinal))
+ {
+ itemTypeName = name.Substring(
+ listPrefix.Length, name.Length - listPrefix.Length - 1);
+ return true;
+ }
+
+ itemTypeName = null;
+ return false;
+ }
+
+ private bool TryGetNullableItemTypeName(string name, out string itemTypeName)
+ {
+ const string nullablePrefix = "Nullable<";
+ const string fullNullablePrefix = "System.Nullable<";
+
+ if (name.StartsWith(fullNullablePrefix, StringComparison.Ordinal))
+ {
+ itemTypeName = name.Substring(
+ fullNullablePrefix.Length, name.Length - fullNullablePrefix.Length - 1);
+ return true;
+ }
+
+ if (name.StartsWith(nullablePrefix, StringComparison.Ordinal))
+ {
+ itemTypeName = name.Substring(
+ nullablePrefix.Length, name.Length - nullablePrefix.Length - 1);
+ return true;
+ }
+
+ itemTypeName = null;
+ return false;
+ }
+
/// <summary>
/// Gets the type for the specified schema
/// </summary>
diff --git a/lang/csharp/src/apache/test/Specific/EmbeddedGenericRecordUser.cs b/lang/csharp/src/apache/test/Specific/EmbeddedGenericRecordUser.cs
new file mode 100644
index 0000000..149ba94
--- /dev/null
+++ b/lang/csharp/src/apache/test/Specific/EmbeddedGenericRecordUser.cs
@@ -0,0 +1,73 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// ------------------------------------------------------------------------------
+// <auto-generated>
+// Generated by avrogen, version 1.10.0.0
+// Changes to this file may cause incorrect behavior and will be lost if code
+// is regenerated
+// </auto-generated>
+// ------------------------------------------------------------------------------
+namespace Avro.Test.Specific
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using Avro;
+ using Avro.Specific;
+
+ public partial class EmbeddedGenericRecordUser : ISpecificRecord
+ {
+ public static Schema _SCHEMA = Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"EmbeddedGenericRecordUser\",\"namespace\":\"Avro.Test.Specif" +
+ "ic\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"}]}");
+ private string _name;
+ public virtual Schema Schema
+ {
+ get
+ {
+ return EmbeddedGenericRecordUser._SCHEMA;
+ }
+ }
+ public string name
+ {
+ get
+ {
+ return this._name;
+ }
+ set
+ {
+ this._name = value;
+ }
+ }
+ public virtual object Get(int fieldPos)
+ {
+ switch (fieldPos)
+ {
+ case 0: return this.name;
+ default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Get()");
+ };
+ }
+ public virtual void Put(int fieldPos, object fieldValue)
+ {
+ switch (fieldPos)
+ {
+ case 0: this.name = (System.String)fieldValue; break;
+ default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Put()");
+ };
+ }
+ }
+}
diff --git a/lang/csharp/src/apache/test/Specific/EmbeddedGenericsRecord.cs b/lang/csharp/src/apache/test/Specific/EmbeddedGenericsRecord.cs
index c6595a2..82b5959 100644
--- a/lang/csharp/src/apache/test/Specific/EmbeddedGenericsRecord.cs
+++ b/lang/csharp/src/apache/test/Specific/EmbeddedGenericsRecord.cs
@@ -1,4 +1,4 @@
-/*
+/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
@@ -32,11 +32,14 @@ namespace Avro.Test.Specific
public partial class EmbeddedGenericsRecord : ISpecificRecord
{
- public static Schema _SCHEMA = Avro.Schema.Parse(@"{""type"":""record"",""name"":""EmbeddedGenericsRecord"",""namespace"":""Avro.Test.Specific"",""fields"":[{""name"":""OptionalInt"",""type"":[""null"",""int""]},{""name"":""OptionalIntList"",""type"":{""type"":""array"",""items"":[""null"",""int""]}},{""name"":""OptionalIntMatrix"",""type"":{""type"":""array"",""items"":{""type"":""array"",""items"":{""type"":""array"",""items"":[""null"",""int""]}}}},{""name"":""IntMatrix"",""type"":{ [...]
+ public static Schema _SCHEMA = Avro.Schema.Parse(@"{""type"":""record"",""name"":""EmbeddedGenericsRecord"",""namespace"":""Avro.Test.Specific"",""fields"":[{""name"":""OptionalInt"",""type"":[""null"",""int""]},{""name"":""OptionalIntList"",""type"":{""type"":""array"",""items"":[""null"",""int""]}},{""name"":""OptionalUserList"",""type"":{""type"":""array"",""items"":[""null"",{""type"":""record"",""name"":""EmbeddedGenericRecordUser"",""namespace"":""Avro.Test.Specific"",""fields"": [...]
private System.Nullable<System.Int32> _OptionalInt;
private IList<System.Nullable<System.Int32>> _OptionalIntList;
+ private IList<Avro.Test.Specific.EmbeddedGenericRecordUser> _OptionalUserList;
private IList<IList<IList<System.Nullable<System.Int32>>>> _OptionalIntMatrix;
+ private IList<IList<IList<Avro.Test.Specific.EmbeddedGenericRecordUser>>> _OptionalUserMatrix;
private IList<IList<IList<System.Int32>>> _IntMatrix;
+ private IList<IList<IList<Avro.Test.Specific.EmbeddedGenericRecordUser>>> _UserMatrix;
public virtual Schema Schema
{
get
@@ -66,6 +69,17 @@ namespace Avro.Test.Specific
this._OptionalIntList = value;
}
}
+ public IList<Avro.Test.Specific.EmbeddedGenericRecordUser> OptionalUserList
+ {
+ get
+ {
+ return this._OptionalUserList;
+ }
+ set
+ {
+ this._OptionalUserList = value;
+ }
+ }
public IList<IList<IList<System.Nullable<System.Int32>>>> OptionalIntMatrix
{
get
@@ -77,6 +91,17 @@ namespace Avro.Test.Specific
this._OptionalIntMatrix = value;
}
}
+ public IList<IList<IList<Avro.Test.Specific.EmbeddedGenericRecordUser>>> OptionalUserMatrix
+ {
+ get
+ {
+ return this._OptionalUserMatrix;
+ }
+ set
+ {
+ this._OptionalUserMatrix = value;
+ }
+ }
public IList<IList<IList<System.Int32>>> IntMatrix
{
get
@@ -88,14 +113,28 @@ namespace Avro.Test.Specific
this._IntMatrix = value;
}
}
+ public IList<IList<IList<Avro.Test.Specific.EmbeddedGenericRecordUser>>> UserMatrix
+ {
+ get
+ {
+ return this._UserMatrix;
+ }
+ set
+ {
+ this._UserMatrix = value;
+ }
+ }
public virtual object Get(int fieldPos)
{
switch (fieldPos)
{
case 0: return this.OptionalInt;
case 1: return this.OptionalIntList;
- case 2: return this.OptionalIntMatrix;
- case 3: return this.IntMatrix;
+ case 2: return this.OptionalUserList;
+ case 3: return this.OptionalIntMatrix;
+ case 4: return this.OptionalUserMatrix;
+ case 5: return this.IntMatrix;
+ case 6: return this.UserMatrix;
default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Get()");
};
}
@@ -105,8 +144,11 @@ namespace Avro.Test.Specific
{
case 0: this.OptionalInt = (System.Nullable<System.Int32>)fieldValue; break;
case 1: this.OptionalIntList = (IList<System.Nullable<System.Int32>>)fieldValue; break;
- case 2: this.OptionalIntMatrix = (IList<IList<IList<System.Nullable<System.Int32>>>>)fieldValue; break;
- case 3: this.IntMatrix = (IList<IList<IList<System.Int32>>>)fieldValue; break;
+ case 2: this.OptionalUserList = (IList<Avro.Test.Specific.EmbeddedGenericRecordUser>)fieldValue; break;
+ case 3: this.OptionalIntMatrix = (IList<IList<IList<System.Nullable<System.Int32>>>>)fieldValue; break;
+ case 4: this.OptionalUserMatrix = (IList<IList<IList<Avro.Test.Specific.EmbeddedGenericRecordUser>>>)fieldValue; break;
+ case 5: this.IntMatrix = (IList<IList<IList<System.Int32>>>)fieldValue; break;
+ case 6: this.UserMatrix = (IList<IList<IList<Avro.Test.Specific.EmbeddedGenericRecordUser>>>)fieldValue; break;
default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Put()");
};
}
diff --git a/lang/csharp/src/apache/test/Specific/ObjectCreatorTests.cs b/lang/csharp/src/apache/test/Specific/ObjectCreatorTests.cs
index f17ff55..d6b29d5 100644
--- a/lang/csharp/src/apache/test/Specific/ObjectCreatorTests.cs
+++ b/lang/csharp/src/apache/test/Specific/ObjectCreatorTests.cs
@@ -62,22 +62,34 @@ namespace Avro.Test.Specific
objectCreator.GetType("ThisTypeDoesNotExist", Schema.Type.Record));
}
- [Test]
- public void TestGetType()
+ [TestCase("Foo", Schema.Type.Record, typeof(Foo))]
+ public void TestGetTypeEquals(string name, Schema.Type schemaType, Type expected)
{
var objectCreator = new ObjectCreator();
+ var actual = objectCreator.GetType(name, Schema.Type.Record);
- // Single Foo
- Assert.AreEqual(typeof(Foo),
- objectCreator.GetType("Foo", Schema.Type.Record));
+ Assert.AreEqual(expected, actual);
+ }
- // Array of Foo
- Assert.True(typeof(IList<Foo>).IsAssignableFrom(
- objectCreator.GetType("Foo", Schema.Type.Array)));
+ [TestCase("Foo", Schema.Type.Array, typeof(IList<Foo>))]
+ [TestCase("IList<Foo>", Schema.Type.Array, typeof(IList<IList<Foo>>))]
+ [TestCase("IList<IList<IList<Foo>>>", Schema.Type.Array, typeof(IList<IList<IList<IList<Foo>>>>))]
+ [TestCase("System.Collections.Generic.IList<System.Collections.Generic.IList<System.Collections.Generic.IList<Foo>>>", Schema.Type.Array, typeof(IList<IList<IList<IList<Foo>>>>))]
+ [TestCase("Foo", Schema.Type.Map, typeof(IDictionary<string, Foo>))]
+ [TestCase("Nullable<Int32>", Schema.Type.Array, typeof(IList<Nullable<int>>))]
+ [TestCase("System.Nullable<Int32>", Schema.Type.Array, typeof(IList<int?>))]
+ [TestCase("IList<Nullable<Int32>>", Schema.Type.Array, typeof(IList<IList<int?>>))]
+ [TestCase("IList<System.Nullable<Int32>>", Schema.Type.Array, typeof(IList<IList<int?>>))]
+ public void TestGetTypeAssignable(string name, Schema.Type schemaType, Type expected)
+ {
+ var objectCreator = new ObjectCreator();
+ var actual = objectCreator.GetType(name, schemaType);
- // Map of Foo
- Assert.True(typeof(IDictionary<string, Foo>).IsAssignableFrom(
- objectCreator.GetType("Foo", Schema.Type.Map)));
+ Assert.True(
+ expected.IsAssignableFrom(actual),
+ " Expected: assignable from {0}\n But was: {1}",
+ expected,
+ actual);
}
[TestCase(typeof(MyNullableFoo), "MyNullableFoo",
diff --git a/lang/csharp/src/apache/test/Specific/SpecificTests.cs b/lang/csharp/src/apache/test/Specific/SpecificTests.cs
index 9da4b83..96d412b 100644
--- a/lang/csharp/src/apache/test/Specific/SpecificTests.cs
+++ b/lang/csharp/src/apache/test/Specific/SpecificTests.cs
@@ -254,6 +254,15 @@ namespace Avro.Test
var srcRecord = new EmbeddedGenericsRecord
{
OptionalIntList = new List<int?> { 1, 2, null, 3, null, null },
+ OptionalUserList = new List<EmbeddedGenericRecordUser>
+ {
+ new EmbeddedGenericRecordUser { name = "1" },
+ new EmbeddedGenericRecordUser { name = "2" },
+ null,
+ new EmbeddedGenericRecordUser { name = "3" },
+ null,
+ null,
+ },
OptionalIntMatrix = new List<IList<IList<int?>>>
{
new List<IList<int?>>
@@ -267,6 +276,27 @@ namespace Avro.Test
},
new List<IList<int?>> { },
},
+ OptionalUserMatrix = new List<IList<IList<EmbeddedGenericRecordUser>>>
+ {
+ new List<IList<EmbeddedGenericRecordUser>>
+ {
+ new List<EmbeddedGenericRecordUser>
+ {
+ null,
+ new EmbeddedGenericRecordUser { name = "2" },
+ },
+ new List<EmbeddedGenericRecordUser> { null, null },
+ },
+ new List<IList<EmbeddedGenericRecordUser>>
+ {
+ new List<EmbeddedGenericRecordUser>
+ {
+ new EmbeddedGenericRecordUser { name = "5" },
+ new EmbeddedGenericRecordUser { name = "6" },
+ },
+ },
+ new List<IList<EmbeddedGenericRecordUser>> { },
+ },
IntMatrix = new List<IList<IList<int>>>
{
new List<IList<int>>
@@ -279,6 +309,31 @@ namespace Avro.Test
new List<int> { 5, 6, },
},
new List<IList<int>> { },
+ },
+ UserMatrix = new List<IList<IList<EmbeddedGenericRecordUser>>>
+ {
+ new List<IList<EmbeddedGenericRecordUser>>
+ {
+ new List<EmbeddedGenericRecordUser>
+ {
+ new EmbeddedGenericRecordUser { name = "1" },
+ new EmbeddedGenericRecordUser { name = "2" },
+ },
+ new List<EmbeddedGenericRecordUser>
+ {
+ new EmbeddedGenericRecordUser { name = "3" },
+ new EmbeddedGenericRecordUser { name = "4" },
+ },
+ },
+ new List<IList<EmbeddedGenericRecordUser>>
+ {
+ new List<EmbeddedGenericRecordUser>
+ {
+ new EmbeddedGenericRecordUser { name = "5" },
+ new EmbeddedGenericRecordUser { name = "6" },
+ },
+ },
+ new List<IList<EmbeddedGenericRecordUser>> { },
}
};
var stream = serialize(EmbeddedGenericsRecord._SCHEMA, srcRecord);
@@ -292,6 +347,14 @@ namespace Avro.Test
Assert.AreEqual(3, dstRecord.OptionalIntList[3]);
Assert.AreEqual(null, dstRecord.OptionalIntList[4]);
Assert.AreEqual(null, dstRecord.OptionalIntList[5]);
+
+ Assert.AreEqual("1", dstRecord.OptionalUserList[0].name);
+ Assert.AreEqual("2", dstRecord.OptionalUserList[1].name);
+ Assert.AreEqual(null, dstRecord.OptionalUserList[2]);
+ Assert.AreEqual("3", dstRecord.OptionalUserList[3].name);
+ Assert.AreEqual(null, dstRecord.OptionalUserList[4]);
+ Assert.AreEqual(null, dstRecord.OptionalUserList[5]);
+
Assert.AreEqual(null, dstRecord.OptionalIntMatrix[0][0][0]);
Assert.AreEqual(2, dstRecord.OptionalIntMatrix[0][0][1]);
Assert.AreEqual(null, dstRecord.OptionalIntMatrix[0][1][0]);
@@ -299,6 +362,15 @@ namespace Avro.Test
Assert.AreEqual(5, dstRecord.OptionalIntMatrix[1][0][0]);
Assert.AreEqual(6, dstRecord.OptionalIntMatrix[1][0][1]);
Assert.AreEqual(0, dstRecord.OptionalIntMatrix[2].Count);
+
+ Assert.AreEqual(null, dstRecord.OptionalUserMatrix[0][0][0]);
+ Assert.AreEqual("2", dstRecord.OptionalUserMatrix[0][0][1].name);
+ Assert.AreEqual(null, dstRecord.OptionalUserMatrix[0][1][0]);
+ Assert.AreEqual(null, dstRecord.OptionalUserMatrix[0][1][1]);
+ Assert.AreEqual("5", dstRecord.OptionalUserMatrix[1][0][0].name);
+ Assert.AreEqual("6", dstRecord.OptionalUserMatrix[1][0][1].name);
+ Assert.AreEqual(0, dstRecord.OptionalUserMatrix[2].Count);
+
Assert.AreEqual(1, dstRecord.IntMatrix[0][0][0]);
Assert.AreEqual(2, dstRecord.IntMatrix[0][0][1]);
Assert.AreEqual(3, dstRecord.IntMatrix[0][1][0]);
@@ -306,6 +378,14 @@ namespace Avro.Test
Assert.AreEqual(5, dstRecord.IntMatrix[1][0][0]);
Assert.AreEqual(6, dstRecord.IntMatrix[1][0][1]);
Assert.AreEqual(0, dstRecord.IntMatrix[2].Count);
+
+ Assert.AreEqual("1", dstRecord.UserMatrix[0][0][0].name);
+ Assert.AreEqual("2", dstRecord.UserMatrix[0][0][1].name);
+ Assert.AreEqual("3", dstRecord.UserMatrix[0][1][0].name);
+ Assert.AreEqual("4", dstRecord.UserMatrix[0][1][1].name);
+ Assert.AreEqual("5", dstRecord.UserMatrix[1][0][0].name);
+ Assert.AreEqual("6", dstRecord.UserMatrix[1][0][1].name);
+ Assert.AreEqual(0, dstRecord.UserMatrix[2].Count);
}
private static S deserialize<S>(Stream ms, Schema ws, Schema rs) where S : class, ISpecificRecord
@@ -355,6 +435,12 @@ namespace Avro.Test
private static void AssertSpecificRecordEqual(ISpecificRecord rec1, ISpecificRecord rec2)
{
+ if (rec1 == null && rec2 == null)
+ {
+ // Both are null, that's equivalent.
+ return;
+ }
+
var recordSchema = (RecordSchema) rec1.Schema;
for (int i = 0; i < recordSchema.Count; i++)
{
@@ -366,20 +452,7 @@ namespace Avro.Test
}
else if (rec1Val is IList)
{
- var rec1List = (IList) rec1Val;
- if( rec1List.Count > 0 && rec1List[0] is ISpecificRecord)
- {
- var rec2List = (IList) rec2Val;
- Assert.AreEqual(rec1List.Count, rec2List.Count);
- for (int j = 0; j < rec1List.Count; j++)
- {
- AssertSpecificRecordEqual((ISpecificRecord)rec1List[j], (ISpecificRecord)rec2List[j]);
- }
- }
- else
- {
- Assert.AreEqual(rec1Val, rec2Val);
- }
+ AssertListEqual((IList)rec1Val, (IList)rec2Val);
}
else if (rec1Val is IDictionary)
{
@@ -406,6 +479,53 @@ namespace Avro.Test
}
}
}
+
+ /// <summary>
+ /// Asserts that two lists are equal, delegating the work of comapring
+ /// <see cref="ISpecificRecord"/> entries to
+ /// <see cref="AssertSpecificRecordEqual(ISpecificRecord, ISpecificRecord)"/>.
+ /// </summary>
+ /// <param name="expected">Expected list value.</param>
+ /// <param name="actual">Actual list value.</param>
+ private static void AssertListEqual(IList expected, IList actual)
+ {
+ Assert.AreEqual(expected.Count, actual.Count);
+
+ for (var i = 0; i < expected.Count; ++i)
+ {
+ // Perform null checks first
+ if (expected[i] == null)
+ {
+ Assert.Null(actual[i]);
+ continue;
+ }
+ else
+ {
+ Assert.NotNull(actual[i]);
+ }
+
+ if (expected[i] is ISpecificRecord expectedRecord)
+ {
+ var actualRecord = actual[i] as ISpecificRecord;
+
+ Assert.NotNull(actualRecord, "Expected entry that implements ISpecificRecord," +
+ $" but was {actual[i].GetType().Name}");
+ AssertSpecificRecordEqual(expectedRecord, actualRecord);
+ }
+ else if (expected[i] is IList expectedList)
+ {
+ var actualList = actual[i] as IList;
+
+ Assert.NotNull(actualList, "Expected entry that implements IList," +
+ $" but was {actual[i].GetType().Name}");
+ AssertListEqual(expectedList, actualList);
+ }
+ else
+ {
+ Assert.AreEqual(expected, actual);
+ }
+ }
+ }
}
enum EnumType