diff --git a/MoreLinq.Test.Aot/MoreLinq.Test.Aot.csproj b/MoreLinq.Test.Aot/MoreLinq.Test.Aot.csproj
new file mode 100644
index 000000000..9453f8dc7
--- /dev/null
+++ b/MoreLinq.Test.Aot/MoreLinq.Test.Aot.csproj
@@ -0,0 +1,32 @@
+
+
+
+ net8.0
+ exe
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoreLinq.Test.Aot/ToDataTableTest.cs b/MoreLinq.Test.Aot/ToDataTableTest.cs
new file mode 100644
index 000000000..163c76598
--- /dev/null
+++ b/MoreLinq.Test.Aot/ToDataTableTest.cs
@@ -0,0 +1,235 @@
+#region License and Terms
+// MoreLINQ - Extensions to LINQ to Objects
+// Copyright (c) 2024 Atif Aziz. All rights reserved.
+//
+// Licensed 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
+//
+// http://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.
+#endregion
+
+namespace MoreLinq.Test
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Data;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using System.Linq.Expressions;
+
+ [TestClass]
+ public class ToDataTableTest
+ {
+ sealed class TestObject(int key)
+ {
+ public int KeyField = key;
+ public Guid? ANullableGuidField = Guid.NewGuid();
+
+ public string AString { get; } = "ABCDEFGHIKKLMNOPQRSTUVWXYSZ";
+ public decimal? ANullableDecimal { get; } = key / 3;
+ public object Unreadable { set => throw new NotImplementedException(); }
+
+ public object this[int index] { get => new(); set { } }
+
+ public override string ToString() => nameof(TestObject);
+ }
+
+ readonly IReadOnlyCollection testObjects;
+
+ public ToDataTableTest() =>
+ this.testObjects = Enumerable.Range(0, 3)
+ .Select(i => new TestObject(i))
+ .ToArray();
+
+ [TestMethod]
+ public void ToDataTableNullMemberExpressionMethod()
+ {
+ Expression>? expression = null;
+
+ [UnconditionalSuppressMessage("Aot", "IL2026")]
+ void Act() => _ = this.testObjects.ToDataTable(expression!);
+
+ var e = Assert.ThrowsException(Act);
+ Assert.AreEqual("expressions", e.ParamName);
+ }
+
+ [TestMethod]
+ public void ToDataTableTableWithWrongColumnNames()
+ {
+ using var dt = new DataTable();
+ _ = dt.Columns.Add("Test");
+
+ [UnconditionalSuppressMessage("Aot", "IL2026")]
+ void Act() => _ = this.testObjects.ToDataTable(dt);
+
+ var e = Assert.ThrowsException(Act);
+ Assert.AreEqual("table", e.ParamName);
+ }
+
+ [TestMethod]
+ public void ToDataTableTableWithWrongColumnDataType()
+ {
+ using var dt = new DataTable();
+ _ = dt.Columns.Add("AString", typeof(int));
+
+ [UnconditionalSuppressMessage("Aot", "IL2026")]
+ void Act() => _ = this.testObjects.ToDataTable(dt, t => t.AString);
+
+ var e = Assert.ThrowsException(Act);
+ Assert.AreEqual("table", e.ParamName);
+ }
+
+ void TestDataTableMemberExpression(Expression> expression)
+ {
+ [UnconditionalSuppressMessage("Aot", "IL2026")]
+ void Act() => _ = this.testObjects.ToDataTable(expression);
+
+ var e = Assert.ThrowsException(Act);
+ Assert.AreEqual("expressions", e.ParamName);
+ var innerException = e.InnerException;
+ Assert.IsNotNull(innerException);
+ Assert.IsInstanceOfType(innerException);
+ Assert.AreEqual("lambda", ((ArgumentException)innerException).ParamName);
+ }
+
+ [TestMethod]
+ public void ToDataTableMemberExpressionMethod()
+ {
+ TestDataTableMemberExpression(t => t.ToString());
+ }
+
+ [TestMethod]
+ public void ToDataTableMemberExpressionNonMember()
+ {
+ TestDataTableMemberExpression(t => t.ToString().Length);
+ }
+
+ [TestMethod]
+ public void ToDataTableMemberExpressionIndexer()
+ {
+ TestDataTableMemberExpression(t => t[0]);
+ }
+
+ [TestMethod]
+ public void ToDataTableMemberExpressionStatic()
+ {
+ TestDataTableMemberExpression(_ => DateTime.Now);
+ }
+
+ [TestMethod]
+ public void ToDataTableSchemaInDeclarationOrder()
+ {
+ [UnconditionalSuppressMessage("Aot", "IL2026")]
+ DataTable Act() => this.testObjects.ToDataTable();
+
+ var dt = Act();
+
+ // Assert properties first, then fields, then in declaration order
+
+ Assert.AreEqual("KeyField", dt.Columns[2].Caption);
+ Assert.AreEqual(typeof(int), dt.Columns[2].DataType);
+
+ Assert.AreEqual("ANullableGuidField", dt.Columns[3].Caption);
+ Assert.AreEqual(typeof(Guid), dt.Columns[3].DataType);
+ Assert.IsTrue(dt.Columns[3].AllowDBNull);
+
+ Assert.AreEqual("AString", dt.Columns[0].Caption);
+ Assert.AreEqual(typeof(string), dt.Columns[0].DataType);
+
+ Assert.AreEqual("ANullableDecimal", dt.Columns[1].Caption);
+ Assert.AreEqual(typeof(decimal), dt.Columns[1].DataType);
+
+ Assert.AreEqual(4, dt.Columns.Count);
+ }
+
+ [TestMethod]
+ public void ToDataTableContainsAllElements()
+ {
+ [UnconditionalSuppressMessage("Aot", "IL2026")]
+ DataTable Act() => this.testObjects.ToDataTable();
+
+ var dt = Act();
+
+ Assert.AreEqual(this.testObjects.Count, dt.Rows.Count);
+ }
+
+ [TestMethod]
+ public void ToDataTableWithExpression()
+ {
+ [UnconditionalSuppressMessage("Aot", "IL2026")]
+ DataTable Act() => this.testObjects.ToDataTable(t => t.AString);
+
+ var dt = Act();
+
+ Assert.AreEqual("AString", dt.Columns[0].Caption);
+ Assert.AreEqual(typeof(string), dt.Columns[0].DataType);
+
+ Assert.AreEqual(1, dt.Columns.Count);
+ }
+
+ [TestMethod]
+ public void ToDataTableWithSchema()
+ {
+ using var dt = new DataTable();
+ var columns = dt.Columns;
+ _ = columns.Add("Column1", typeof(int));
+ _ = columns.Add("Value", typeof(string));
+ _ = columns.Add("Column3", typeof(int));
+ _ = columns.Add("Name", typeof(string));
+
+ var vars = Environment.GetEnvironmentVariables()
+ .Cast()
+ .ToArray();
+
+ [UnconditionalSuppressMessage("Aot", "IL2026")]
+ void Act() => _ = vars.Select(e => new { Name = e.Key.ToString(), Value = e.Value!.ToString() })
+ .ToDataTable(dt, e => e.Name, e => e.Value);
+
+ Act();
+
+ var rows = dt.Rows.Cast().ToArray();
+ Assert.AreEqual(vars.Length, rows.Length);
+ CollectionAssert.AreEqual(vars.Select(e => e.Key).ToArray(), rows.Select(r => r["Name"]).ToArray());
+ CollectionAssert.AreEqual(vars.Select(e => e.Value).ToArray(), rows.Select(r => r["Value"]).ToArray());
+ }
+
+ readonly struct Point(int x, int y)
+ {
+#pragma warning disable CA1805 // Do not initialize unnecessarily (avoids CS0649)
+ public static Point Empty = new();
+#pragma warning restore CA1805 // Do not initialize unnecessarily
+ public bool IsEmpty => X == 0 && Y == 0;
+ public int X { get; } = x;
+ public int Y { get; } = y;
+ }
+
+ [TestMethod]
+ public void ToDataTableIgnoresStaticMembers()
+ {
+ [UnconditionalSuppressMessage("Aot", "IL2026")]
+ static DataTable Act() => new[] { new Point(12, 34) }.ToDataTable();
+
+ var points = Act();
+
+ Assert.AreEqual(3, points.Columns.Count);
+ var x = points.Columns["X"];
+ var y = points.Columns["Y"];
+ var empty = points.Columns["IsEmpty"];
+ Assert.IsNotNull(x);
+ Assert.IsNotNull(y);
+ Assert.IsNotNull(empty);
+ var row = points.Rows.Cast().Single();
+ Assert.AreEqual(12, row[x]);
+ Assert.AreEqual(34, row[y]);
+ Assert.AreEqual(row[empty], false);
+ }
+ }
+}
diff --git a/MoreLinq.Test/Throws.cs b/MoreLinq.Test/Throws.cs
index 672234e3c..58877a6fc 100644
--- a/MoreLinq.Test/Throws.cs
+++ b/MoreLinq.Test/Throws.cs
@@ -36,16 +36,19 @@ public static ExactTypeConstraint TypeOf()
NUnit.Framework.Throws.TypeOf();
public static EqualConstraint ArgumentException(string expectedParamName) =>
- NUnit.Framework.Throws.ArgumentException.With.ParamName().EqualTo(expectedParamName);
+ NUnit.Framework.Throws.ArgumentException.With.ParamName(expectedParamName);
public static EqualConstraint ArgumentNullException(string expectedParamName) =>
- NUnit.Framework.Throws.ArgumentNullException.With.ParamName().EqualTo(expectedParamName);
+ NUnit.Framework.Throws.ArgumentNullException.With.ParamName(expectedParamName);
public static ExactTypeConstraint ArgumentOutOfRangeException() =>
TypeOf();
public static EqualConstraint ArgumentOutOfRangeException(string expectedParamName) =>
- ArgumentOutOfRangeException().With.ParamName().EqualTo(expectedParamName);
+ ArgumentOutOfRangeException().With.ParamName(expectedParamName);
+
+ public static EqualConstraint ParamName(this ConstraintExpression constraint, string expectedParamName) =>
+ constraint.ParamName().EqualTo(expectedParamName);
static ResolvableConstraintExpression ParamName(this ConstraintExpression constraint) =>
constraint.Property(nameof(System.ArgumentException.ParamName));
diff --git a/MoreLinq.Test/ToDataTableTest.cs b/MoreLinq.Test/ToDataTableTest.cs
index d6cac4cda..65958f544 100644
--- a/MoreLinq.Test/ToDataTableTest.cs
+++ b/MoreLinq.Test/ToDataTableTest.cs
@@ -77,33 +77,35 @@ public void ToDataTableTableWithWrongColumnDataType()
Throws.ArgumentException("table"));
}
+ void TestDataTableMemberExpression(Expression> expression)
+ {
+ Assert.That(() => this.testObjects.ToDataTable(expression),
+ Throws.ArgumentException("expressions")
+ .And.InnerException.With.ParamName("lambda"));
+ }
+
[Test]
public void ToDataTableMemberExpressionMethod()
{
- Assert.That(() => this.testObjects.ToDataTable(t => t.ToString()),
- Throws.ArgumentException("lambda"));
+ TestDataTableMemberExpression(t => t.ToString());
}
-
[Test]
public void ToDataTableMemberExpressionNonMember()
{
- Assert.That(() => this.testObjects.ToDataTable(t => t.ToString().Length),
- Throws.ArgumentException("lambda"));
+ TestDataTableMemberExpression(t => t.ToString().Length);
}
[Test]
public void ToDataTableMemberExpressionIndexer()
{
- Assert.That(() => this.testObjects.ToDataTable(t => t[0]),
- Throws.ArgumentException("lambda"));
+ TestDataTableMemberExpression(t => t[0]);
}
[Test]
public void ToDataTableMemberExpressionStatic()
{
- Assert.That(() => _ = this.testObjects.ToDataTable(_ => DateTime.Now),
- Throws.ArgumentException("lambda"));
+ TestDataTableMemberExpression(_ => DateTime.Now);
}
[Test]
@@ -113,12 +115,6 @@ public void ToDataTableSchemaInDeclarationOrder()
// Assert properties first, then fields, then in declaration order
- Assert.That(dt.Columns[0].Caption, Is.EqualTo("AString"));
- Assert.That(dt.Columns[0].DataType, Is.EqualTo(typeof(string)));
-
- Assert.That(dt.Columns[1].Caption, Is.EqualTo("ANullableDecimal"));
- Assert.That(dt.Columns[1].DataType, Is.EqualTo(typeof(decimal)));
-
Assert.That(dt.Columns[2].Caption, Is.EqualTo("KeyField"));
Assert.That(dt.Columns[2].DataType, Is.EqualTo(typeof(int)));
@@ -126,6 +122,12 @@ public void ToDataTableSchemaInDeclarationOrder()
Assert.That(dt.Columns[3].DataType, Is.EqualTo(typeof(Guid)));
Assert.That(dt.Columns[3].AllowDBNull, Is.True);
+ Assert.That(dt.Columns[0].Caption, Is.EqualTo("AString"));
+ Assert.That(dt.Columns[0].DataType, Is.EqualTo(typeof(string)));
+
+ Assert.That(dt.Columns[1].Caption, Is.EqualTo("ANullableDecimal"));
+ Assert.That(dt.Columns[1].DataType, Is.EqualTo(typeof(decimal)));
+
Assert.That(dt.Columns.Count, Is.EqualTo(4));
}
diff --git a/MoreLinq.sln b/MoreLinq.sln
index ff1b65d6c..d37b3cefb 100644
--- a/MoreLinq.sln
+++ b/MoreLinq.sln
@@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoreLinq.Test", "MoreLinq.T
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoreLinq.ExtensionsGenerator", "bld\ExtensionsGenerator\MoreLinq.ExtensionsGenerator.csproj", "{5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoreLinq.Test.Aot", "MoreLinq.Test.Aot\MoreLinq.Test.Aot.csproj", "{776973A3-AC2E-423E-8106-B4E296CE7752}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -43,6 +45,10 @@ Global
{5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {776973A3-AC2E-423E-8106-B4E296CE7752}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {776973A3-AC2E-423E-8106-B4E296CE7752}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {776973A3-AC2E-423E-8106-B4E296CE7752}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {776973A3-AC2E-423E-8106-B4E296CE7752}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/MoreLinq/Extensions.ToDataTable.g.cs b/MoreLinq/Extensions.ToDataTable.g.cs
index 20380243d..bf5c9ef7b 100644
--- a/MoreLinq/Extensions.ToDataTable.g.cs
+++ b/MoreLinq/Extensions.ToDataTable.g.cs
@@ -43,7 +43,6 @@ namespace MoreLinq.Extensions
[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
public static partial class ToDataTableExtension
{
-
///
/// Converts a sequence to a object.
///
@@ -54,8 +53,11 @@ public static partial class ToDataTableExtension
///
/// This operator uses immediate execution.
- public static DataTable ToDataTable(this IEnumerable source)
- => MoreEnumerable.ToDataTable(source);
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
+ public static DataTable
+ ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedPublicPropertiesOrFields)] T>(
+ this IEnumerable source)
+ => MoreEnumerable. ToDataTable(source);
///
/// Appends elements in the sequence as rows of a given
@@ -70,8 +72,10 @@ public static DataTable ToDataTable(this IEnumerable source)
///
/// This operator uses immediate execution.
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions)
=> MoreEnumerable.ToDataTable(source, expressions);
+
///
/// Appends elements in the sequence as rows of a given object.
///
@@ -84,9 +88,12 @@ public static DataTable ToDataTable(this IEnumerable source, params Expres
///
/// This operator uses immediate execution.
- public static TTable ToDataTable(this IEnumerable source, TTable table)
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
+ public static TTable
+ ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedPublicPropertiesOrFields)] T,
+ TTable>(this IEnumerable source, TTable table)
where TTable : DataTable
- => MoreEnumerable.ToDataTable(source, table);
+ => MoreEnumerable. ToDataTable(source, table);
///
/// Appends elements in the sequence as rows of a given
@@ -103,6 +110,7 @@ public static TTable ToDataTable(this IEnumerable source, TTable t
///
/// This operator uses immediate execution.
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
public static TTable ToDataTable(this IEnumerable source, TTable table, params Expression>[] expressions)
where TTable : DataTable
=> MoreEnumerable.ToDataTable(source, table, expressions);
diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj
index 6c1c604b5..8604ca538 100644
--- a/MoreLinq/MoreLinq.csproj
+++ b/MoreLinq/MoreLinq.csproj
@@ -126,6 +126,7 @@
true
MoreLinq
Library
+ true
key.snk
true
true
@@ -170,9 +171,13 @@
System.Index;
System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute;
System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute;
+ System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes;
+ System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute;
System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute;
System.Diagnostics.CodeAnalysis.MemberNotNullAttribute;
System.Diagnostics.CodeAnalysis.NotNullWhenAttribute;
+ System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute;
+ System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute;
System.Runtime.CompilerServices.CallerArgumentExpressionAttribute;
@@ -211,6 +216,10 @@
$(DefineConstants);NO_ASYNC_STREAMS;NO_BUFFERS
+
+ $(DefineConstants);DYNAMIC_CODE_FALLBACK
+
+
diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs
index ba2275956..1872c3d85 100644
--- a/MoreLinq/ToDataTable.cs
+++ b/MoreLinq/ToDataTable.cs
@@ -20,61 +20,83 @@ namespace MoreLinq
using System;
using System.Collections.Generic;
using System.Data;
+ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
+ using static Diagnostics;
static partial class MoreEnumerable
{
///
- /// Appends elements in the sequence as rows of a given object.
+ /// Converts a sequence to a object.
///
/// The type of the elements of .
- ///
/// The source.
- ///
///
- /// A or subclass representing the source.
+ /// A representing the source.
///
/// This operator uses immediate execution.
- public static TTable ToDataTable(this IEnumerable source, TTable table)
- where TTable : DataTable
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
+ public static DataTable
+ ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedPublicPropertiesOrFields)] T>(
+ this IEnumerable source)
{
- return ToDataTable(source, table, []);
+ if (source == null) throw new ArgumentNullException(nameof(source));
+
+ return source.ToDataTable(new DataTable());
}
///
- /// Appends elements in the sequence as rows of a given
- /// object with a set of lambda expressions specifying which members (property
- /// or field) of each element in the sequence will supply the column values.
+ /// Appends elements in the sequence as rows of a given object.
///
/// The type of the elements of .
+ ///
/// The source.
- /// Expressions providing access to element members.
+ ///
///
- /// A representing the source.
+ /// A or subclass representing the source.
///
/// This operator uses immediate execution.
- public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions)
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
+ public static TTable
+ ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedPublicPropertiesOrFields)] T,
+ TTable>(this IEnumerable source, TTable table)
+ where TTable : DataTable
{
- return ToDataTable(source, new DataTable(), expressions);
+ if (source == null) throw new ArgumentNullException(nameof(source));
+ if (table == null) throw new ArgumentNullException(nameof(table));
+
+ const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
+
+ var members = typeof(T).GetProperties(bindingFlags)
+ .Where(p => p.CanRead && p.GetIndexParameters().Length == 0)
+ .Cast()
+ .Concat(typeof(T).GetFields(bindingFlags))
+ .ToArray();
+
+ return ToDataTable(source, table, members);
}
///
- /// Converts a sequence to a object.
+ /// Appends elements in the sequence as rows of a given
+ /// object with a set of lambda expressions specifying which members (property
+ /// or field) of each element in the sequence will supply the column values.
///
/// The type of the elements of .
/// The source.
+ /// Expressions providing access to element members.
///
/// A representing the source.
///
/// This operator uses immediate execution.
- public static DataTable ToDataTable(this IEnumerable source)
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
+ public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions)
{
- return ToDataTable(source, new DataTable());
+ return ToDataTable(source, new DataTable(), expressions);
}
///
@@ -92,6 +114,7 @@ public static DataTable ToDataTable(this IEnumerable source)
///
/// This operator uses immediate execution.
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
public static TTable ToDataTable(this IEnumerable source, TTable table, params Expression>[] expressions)
where TTable : DataTable
{
@@ -99,7 +122,13 @@ public static TTable ToDataTable(this IEnumerable source, TTable t
if (table == null) throw new ArgumentNullException(nameof(table));
if (expressions == null) throw new ArgumentNullException(nameof(expressions));
- var members = PrepareMemberInfos(expressions).ToArray();
+ return ToDataTable(source, table, PrepareMemberInfos(expressions));
+ }
+
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
+ static TTable ToDataTable(IEnumerable source, TTable table, MemberInfo[] members)
+ where TTable : DataTable
+ {
var boundMembers = BuildOrBindSchema(table, members);
var shredder = CreateShredder(boundMembers);
@@ -127,19 +156,10 @@ public static TTable ToDataTable(this IEnumerable source, TTable t
return table;
}
- static IEnumerable PrepareMemberInfos(ICollection>> expressions)
+ static MemberInfo[] PrepareMemberInfos(ICollection>> expressions)
{
- //
- // If no lambda expressions supplied then reflect them off the source element type.
- //
-
if (expressions.Count == 0)
- {
- return from m in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance)
- where m.MemberType == MemberTypes.Field
- || m is PropertyInfo { CanRead: true } p && p.GetIndexParameters().Length == 0
- select m;
- }
+ return [];
//
// Ensure none of the expressions is null.
@@ -149,7 +169,7 @@ static IEnumerable PrepareMemberInfos(ICollection
+ [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
static MemberInfo?[] BuildOrBindSchema(DataTable table, MemberInfo[] members)
{
//
@@ -224,8 +245,48 @@ var type when Nullable.GetUnderlyingType(type) is { } ut => ut,
};
}
- static Func CreateShredder(MemberInfo?[] members)
+ [UnconditionalSuppressMessage("Aot", "IL3050:RequiresDynamicCode",
+ Justification = "Falls back to reflection-based member access at run-time if the CLR " +
+ "version does not support dynamic code generation.")]
+ static Func CreateShredder(MemberInfo?[] members)
{
+#if DYNAMIC_CODE_FALLBACK
+
+ //
+ // If the runtime does not support dynamic code generation, then
+ // fall back to reflection-based member access at run-time.
+ //
+ // See also: https://github.com/dotnet/runtime/issues/17973#issuecomment-1330799386
+ //
+
+ if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported)
+ {
+ return obj =>
+ {
+ var values = new object?[members.Length];
+
+ for (var i = 0; i < members.Length; i++)
+ {
+ var member = members[i];
+ values[i] = member switch
+ {
+ null => null,
+ PropertyInfo pi => pi.GetValue(obj),
+ FieldInfo fi => fi.GetValue(obj),
+ _ => throw new UnreachableException(),
+ };
+ }
+
+ return values;
+ };
+ }
+#endif
+
+ //
+ // Otherwise compile a lambda expression that will extract the
+ // values of the specified members from an object instance.
+ //
+
var parameter = Expression.Parameter(typeof(T), "e");
//
@@ -240,7 +301,7 @@ static Func CreateShredder(MemberInfo?[] members)
var array = Expression.NewArrayInit(typeof(object), initializers);
- var lambda = Expression.Lambda>(array, parameter);
+ var lambda = Expression.Lambda>(array, parameter);
return lambda.Compile();
@@ -251,4 +312,27 @@ UnaryExpression CreateMemberAccessor(MemberInfo member)
}
}
}
+
+ namespace Extensions
+ {
+ partial class ToDataTableExtension
+ {
+ internal const DynamicallyAccessedMemberTypes DynamicallyAccessedPublicPropertiesOrFields
+ = DynamicallyAccessedMemberTypes.PublicProperties |
+ DynamicallyAccessedMemberTypes.PublicFields;
+
+ internal const string RequiresUnreferencedCodeMessage =
+ "This method uses reflection to access public properties and fields of the source " +
+ "type, and in turn the types of those properties and fields. That latter could be " +
+ "problematic and require root descriptors for some custom and complex types " +
+ "(although columns usually store simple, scalar types). For more, see: " +
+ "https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options#root-descriptors";
+ }
+ }
+
+ file static class Diagnostics
+ {
+ public const DynamicallyAccessedMemberTypes DynamicallyAccessedPublicPropertiesOrFields = MoreLinq.Extensions.ToDataTableExtension.DynamicallyAccessedPublicPropertiesOrFields;
+ public const string RequiresUnreferencedCodeMessage = MoreLinq.Extensions.ToDataTableExtension.RequiresUnreferencedCodeMessage;
+ }
}
diff --git a/test.cmd b/test.cmd
index a6d28730d..209719ca3 100644
--- a/test.cmd
+++ b/test.cmd
@@ -7,6 +7,10 @@ popd & exit /b %ERRORLEVEL%
setlocal
if not defined SKIP_TEST_BUILD set SKIP_TEST_BUILD=false
if %SKIP_TEST_BUILD%==false call build || exit /b 1
+if not "%~1"=="aot" goto :test-all
+call :test-aot
+exit /b %ERRORLEVEL%
+:test-all
call :clean ^
&& call :test net8.0 Debug ^
&& call :test net8.0 Release ^
@@ -14,7 +18,8 @@ call :clean ^
&& call :test net6.0 Release ^
&& call :test net471 Debug ^
&& call :test net471 Release ^
- && call :report-cover
+ && call :report-cover ^
+ && call :test-aot
exit /b %ERRORLEVEL%
:clean
@@ -51,3 +56,17 @@ dotnet reportgenerator -reports:coverage-*.opencover.xml ^
-targetdir:reports ^
&& type reports\Summary.txt
exit /b %ERRORLEVEL%
+
+:test-aot
+setlocal
+cd MoreLinq.Test.Aot
+dotnet publish
+if not ERRORLEVEL==0 exit /b %ERRORLEVEL%
+set AOT_TEST_PUBLISH_DIR=
+for /f %%d in ('dir /ad /s /b publish') do if not defined AOT_TEST_PUBLISH_DIR set AOT_TEST_PUBLISH_DIR=%%~d
+if not defined AOT_TEST_PUBLISH_DIR (
+ echo>&2 Published binary directory not found!
+ exit /b 1
+)
+"%AOT_TEST_PUBLISH_DIR%\MoreLinq.Test.Aot.exe"
+exit /b %ERRORLEVEL%
diff --git a/test.sh b/test.sh
index 9bd9806a0..8427d1849 100755
--- a/test.sh
+++ b/test.sh
@@ -31,3 +31,5 @@ else
mono MoreLinq.Test/bin/$c/net471/MoreLinq.Test.exe
done
fi
+dotnet publish MoreLinq.Test.Aot
+"$(find MoreLinq.Test.Aot -type d -name publish)/MoreLinq.Test.Aot"