diff --git a/packages/@jsii/dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests.sln.DotSettings.user b/packages/@jsii/dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests.sln.DotSettings.user
index e5df662974..d518dc764e 100644
--- a/packages/@jsii/dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests.sln.DotSettings.user
+++ b/packages/@jsii/dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests.sln.DotSettings.user
@@ -1,4 +1,4 @@
<SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from <Amazon.JSII.Runtime.IntegrationTests>" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
- <Project Location="/Users/rmuller/Development/aws/jsii/packages/@jsii/dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests" Presentation="<Amazon.JSII.Runtime.IntegrationTests>" />
+ <Solution />
</SessionState>
\ No newline at end of file
diff --git a/packages/@jsii/dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/TypeCheckingTests.cs b/packages/@jsii/dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/TypeCheckingTests.cs
new file mode 100644
index 0000000000..0c8b727f13
--- /dev/null
+++ b/packages/@jsii/dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/TypeCheckingTests.cs
@@ -0,0 +1,67 @@
+
+using System;
+using System.Collections.Generic;
+using Amazon.JSII.Tests.CalculatorNamespace;
+using Xunit;
+
+#pragma warning disable CS0612
+
+namespace Amazon.JSII.Runtime.IntegrationTests
+{
+ public sealed class TypeCheckingTests : IClassFixture, IDisposable
+ {
+ const string Prefix = nameof(TypeCheckingTests) + ".";
+
+ private readonly IDisposable _serviceContainerFixture;
+
+ public TypeCheckingTests(ServiceContainerFixture serviceContainerFixture)
+ {
+ _serviceContainerFixture = serviceContainerFixture;
+ }
+
+ void IDisposable.Dispose()
+ {
+ _serviceContainerFixture.Dispose();
+ }
+
+ [Fact(DisplayName = Prefix + nameof(Constructor))]
+ public void Constructor()
+ {
+ var exception = Assert.Throws(() =>
+ new ClassWithCollectionOfUnions(new IDictionary[]
+ {
+ new Dictionary
+ {
+ { "good", new StructA { RequiredString = "present"} },
+ { "bad", $"Not a {nameof(StructA)} or {nameof(StructB)}" },
+ }
+ })
+ );
+ Assert.Equal("Expected argument unionProperty[0][\"bad\"] to be one of: Amazon.JSII.Tests.CalculatorNamespace.IStructA, Amazon.JSII.Tests.CalculatorNamespace.IStructB; received System.String (Parameter 'unionProperty')", exception.Message);
+ }
+
+ [Fact(DisplayName = Prefix + nameof(Setter))]
+ public void Setter()
+ {
+ var subject = new ClassWithCollectionOfUnions(Array.Empty>());
+ var exception = Assert.Throws(() =>
+ subject.UnionProperty = new IDictionary[]
+ {
+ new Dictionary
+ {
+ { "good", new StructA { RequiredString = "present" } },
+ { "bad", $"Not a {nameof(StructA)} or {nameof(StructB)}" },
+ }
+ });
+ Assert.Equal("Expected value[0][\"bad\"] to be one of: Amazon.JSII.Tests.CalculatorNamespace.IStructA, Amazon.JSII.Tests.CalculatorNamespace.IStructB; received System.String (Parameter 'value')", exception.Message);
+ }
+
+ [Fact(DisplayName = Prefix + nameof(StaticMethod))]
+ public void StaticMethod()
+ {
+ var exception = Assert.Throws(() =>
+ StructUnionConsumer.IsStructA("Not a StructA"));
+ Assert.Equal("Expected argument struct to be one of: Amazon.JSII.Tests.CalculatorNamespace.IStructA, Amazon.JSII.Tests.CalculatorNamespace.IStructB; received System.String (Parameter 'struct')", exception.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/@jsii/dotnet-runtime/src/Amazon.JSII.Runtime/Configuration.cs b/packages/@jsii/dotnet-runtime/src/Amazon.JSII.Runtime/Configuration.cs
new file mode 100644
index 0000000000..817697a017
--- /dev/null
+++ b/packages/@jsii/dotnet-runtime/src/Amazon.JSII.Runtime/Configuration.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Amazon.JSII.Runtime
+{
+ public static class Configuration
+ {
+ ///
+ /// Enables or disables runtime type checking of parameters when the original model expects a type union, which
+ /// is represented as object in .NET code.
+ ///
+ ///
+ /// This feature is enabled by default.
+ ///
+ /// This feature may be disabled as a work-around if a bug prevents your application from working correctly, or
+ /// in order to stop paying the performance cost of the runtime type checking.
+ ///
+ ///
+ public static bool RuntimeTypeChecking { get; set; } = true;
+ }
+}
\ No newline at end of file
diff --git a/packages/jsii-calc/lib/compliance.ts b/packages/jsii-calc/lib/compliance.ts
index 69ac7daa09..30cc75e079 100644
--- a/packages/jsii-calc/lib/compliance.ts
+++ b/packages/jsii-calc/lib/compliance.ts
@@ -3050,3 +3050,13 @@ export class TwoMethodsWithSimilarCapitalization {
*/
public readonly fooBAR = 111;
}
+
+export class ClassWithCollectionOfUnions {
+ public constructor(
+ public unionProperty: Array>,
+ ) {}
+}
+
+export interface StructWithCollectionOfUnionts {
+ readonly unionProperty: Array>;
+}
diff --git a/packages/jsii-calc/test/assembly.jsii b/packages/jsii-calc/test/assembly.jsii
index c8af814015..040053d0dc 100644
--- a/packages/jsii-calc/test/assembly.jsii
+++ b/packages/jsii-calc/test/assembly.jsii
@@ -2447,6 +2447,90 @@
],
"symbolId": "lib/compliance:ClassThatImplementsThePrivateInterface"
},
+ "jsii-calc.ClassWithCollectionOfUnions": {
+ "assembly": "jsii-calc",
+ "docs": {
+ "stability": "stable"
+ },
+ "fqn": "jsii-calc.ClassWithCollectionOfUnions",
+ "initializer": {
+ "docs": {
+ "stability": "stable"
+ },
+ "locationInModule": {
+ "filename": "lib/compliance.ts",
+ "line": 3055
+ },
+ "parameters": [
+ {
+ "name": "unionProperty",
+ "type": {
+ "collection": {
+ "elementtype": {
+ "collection": {
+ "elementtype": {
+ "union": {
+ "types": [
+ {
+ "fqn": "jsii-calc.StructA"
+ },
+ {
+ "fqn": "jsii-calc.StructB"
+ }
+ ]
+ }
+ },
+ "kind": "map"
+ }
+ },
+ "kind": "array"
+ }
+ }
+ }
+ ]
+ },
+ "kind": "class",
+ "locationInModule": {
+ "filename": "lib/compliance.ts",
+ "line": 3054
+ },
+ "name": "ClassWithCollectionOfUnions",
+ "properties": [
+ {
+ "docs": {
+ "stability": "stable"
+ },
+ "locationInModule": {
+ "filename": "lib/compliance.ts",
+ "line": 3056
+ },
+ "name": "unionProperty",
+ "type": {
+ "collection": {
+ "elementtype": {
+ "collection": {
+ "elementtype": {
+ "union": {
+ "types": [
+ {
+ "fqn": "jsii-calc.StructA"
+ },
+ {
+ "fqn": "jsii-calc.StructB"
+ }
+ ]
+ }
+ },
+ "kind": "map"
+ }
+ },
+ "kind": "array"
+ }
+ }
+ }
+ ],
+ "symbolId": "lib/compliance:ClassWithCollectionOfUnions"
+ },
"jsii-calc.ClassWithCollections": {
"assembly": "jsii-calc",
"docs": {
@@ -13549,6 +13633,57 @@
"name": "StructUnionConsumer",
"symbolId": "lib/compliance:StructUnionConsumer"
},
+ "jsii-calc.StructWithCollectionOfUnionts": {
+ "assembly": "jsii-calc",
+ "datatype": true,
+ "docs": {
+ "stability": "stable"
+ },
+ "fqn": "jsii-calc.StructWithCollectionOfUnionts",
+ "kind": "interface",
+ "locationInModule": {
+ "filename": "lib/compliance.ts",
+ "line": 3060
+ },
+ "name": "StructWithCollectionOfUnionts",
+ "properties": [
+ {
+ "abstract": true,
+ "docs": {
+ "stability": "stable"
+ },
+ "immutable": true,
+ "locationInModule": {
+ "filename": "lib/compliance.ts",
+ "line": 3061
+ },
+ "name": "unionProperty",
+ "type": {
+ "collection": {
+ "elementtype": {
+ "collection": {
+ "elementtype": {
+ "union": {
+ "types": [
+ {
+ "fqn": "jsii-calc.StructA"
+ },
+ {
+ "fqn": "jsii-calc.StructB"
+ }
+ ]
+ }
+ },
+ "kind": "map"
+ }
+ },
+ "kind": "array"
+ }
+ }
+ }
+ ],
+ "symbolId": "lib/compliance:StructWithCollectionOfUnionts"
+ },
"jsii-calc.StructWithEnum": {
"assembly": "jsii-calc",
"datatype": true,
@@ -17653,5 +17788,5 @@
}
},
"version": "3.20.120",
- "fingerprint": "P+E1ta4n5zaxREzT7CqW574NyC10CNmKv5NAwZ5pRt8="
+ "fingerprint": "4J4zpGOrJJsMNdeBDVxr5OLa4LaClMA61HDRI4Wnajg="
}
\ No newline at end of file
diff --git a/packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts b/packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts
index 47a35d6507..ceaec72961 100644
--- a/packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts
+++ b/packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts
@@ -386,13 +386,26 @@ export class DotNetGenerator extends Generator {
// Abstract classes have protected constructors.
const visibility = cls.abstract ? 'protected' : 'public';
+ this.code.openBlock(
+ `${visibility} ${className}(${parametersDefinition}): base(_MakeDeputyProps(${parametersBase}))`,
+ );
+ this.code.closeBlock();
+ this.code.line();
+
+ // This private method is injected so we can validate arguments before deferring to the base constructor, where
+ // the instance will be created in the kernel (where it'd fail on a sub-optimal error instead)...
+ this.code.line(
+ '[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]',
+ );
+ this.code.openBlock(
+ `private static DeputyProps _MakeDeputyProps(${parametersDefinition})`,
+ );
+ this.emitUnionParameterValdation(initializer.parameters);
const args =
parametersBase.length > 0
? `new object?[]{${parametersBase}}`
: `System.Array.Empty