You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the consuming assembly is marked [assembly: CLSCompliant(true)], every CsWin32-generated COM struct that decorates a method parameter with [MarshalAs(UnmanagedType.SafeArray, SafeArraySubTypes = new[] { ... })] produces
warning CS3016: Arrays as attribute arguments is not CLS-compliant
Examples in the generated surface include ITypeInfo, ITypeLib, IRecordInfo, IStream, IClassFactory, etc. Under TreatWarningsAsErrors=true (the default for official builds in many repos, including dotnet/msbuild) these become hard errors.
A blanket <NoWarn>CS3016</NoWarn> masks legitimate user code violations.
The generated structs are internal, so [CLSCompliant(false)] is semantically meaningless on them — but applying it via a hand-written partial declaration is currently the only targeted way to silence the diagnostic, at the cost of then
having to suppress CS3019 ("CLS compliance checking will not be performed … because it is not visible from outside this assembly") for that same folder.
Expected
CsWin32-generated COM struct wrappers should not produce CLS-compliance warnings in a CLS-compliant assembly. They are internal and not part of the assembly's CLS surface; the generator could mark them [CLSCompliant(false)] (or omit the SafeArraySubTypes array, or emit the structs in a form the CLS rules accept) so consumers do not have to maintain a hand-written [CLSCompliant(false)] partial for each generated COM type.
Actual
Each generated COM struct that has a SafeArraySubTypes-decorated parameter emits CS3016 at every such call-site declaration. Consumers must either:
disable CS3016 globally (loses real diagnostic value for user code), or
maintain a per-struct [CLSCompliant(false)] internal partial struct Foo { } declaration alongside an .editorconfig rule that suppresses the resulting CS3019 noise.
Environment
Component
Version
Microsoft.Windows.CsWin32
0.3.275
.NET SDK
10.0.100
TFMs in repro
net10.0;net472
How the workaround looks in production
dotnet/msbuild carries a dedicated file, src/Framework/Windows/Win32/GeneratedInteropClsCompliance.cs, that exists solely to attach [CLSCompliant(false)] to each generated COM struct that trips CS3016, plus an .editorconfig rule that suppresses the resulting CS3019 warnings for files under the CsWin32-generated namespaces.
Emit [CLSCompliant(false)] on generated COM struct wrappers that contain non-CLS-compliant attribute arguments (or otherwise structure the generated [MarshalAs(...)] usage so the array argument is not required). Either change removes the need for consumers to author per-struct partial declarations.
Sources
Drop these four files into a fresh folder and run dotnet build.
usingSystem;[assembly:CLSCompliant(true)]internalstaticclassProgram{// The mere presence of the generated ITypeInfo / ITypeLib / IRecordInfo// structs surfaces CS3016. No call site is required.privatestaticvoidMain()=>Console.WriteLine("see warnings above");}
Sketch of the fix
In the CsWin32 generator, when emitting a COM struct wrapper that contains any parameter decorated with [MarshalAs(UnmanagedType.SafeArray, SafeArraySubTypes = new[] { ... })] (or any other attribute whose argument is an array), also emit [CLSCompliant(false)] on the struct declaration itself:
Because the structs are internal, this attribute is semantically a no-op for the assembly's CLS surface but suppresses CS3016 at the source. The generator should not also need to suppress CS3019 — that warning only fires when the
attribute is applied to a member that the compiler thinks isn't externally visible and the assembly is CLS-compliant. Suppression of CS3019 for generated files remains the consumer's responsibility (e.g. via an .editorconfig rule scoped to the generated namespace), but is one-line and stable across new COM types.
Alternative: emit SafeArraySubTypes as a single-valued attribute argument when only one subtype is needed, or omit it entirely when the generator can prove it is unnecessary for blittable signatures (allowMarshaling: false).
Summary
When the consuming assembly is marked
[assembly: CLSCompliant(true)], every CsWin32-generated COM struct that decorates a method parameter with[MarshalAs(UnmanagedType.SafeArray, SafeArraySubTypes = new[] { ... })]producesExamples in the generated surface include
ITypeInfo,ITypeLib,IRecordInfo,IStream,IClassFactory, etc. UnderTreatWarningsAsErrors=true(the default for official builds in many repos, including dotnet/msbuild) these become hard errors.The warnings cannot be cleanly suppressed:
[SuppressMessage]in a global suppressions file does not work forCSxxxxwarnings — the C# compiler only honorsSuppressMessageAttributefor analyzer diagnostics. See CLSCompliant(true) check for arrays as attribute arguments triggers on internal types dotnet/roslyn#68526.<NoWarn>CS3016</NoWarn>masks legitimate user code violations.internal, so[CLSCompliant(false)]is semantically meaningless on them — but applying it via a hand-writtenpartialdeclaration is currently the only targeted way to silence the diagnostic, at the cost of thenhaving to suppress
CS3019("CLS compliance checking will not be performed … because it is not visible from outside this assembly") for that same folder.Expected
CsWin32-generated COM struct wrappers should not produce CLS-compliance warnings in a CLS-compliant assembly. They are
internaland not part of the assembly's CLS surface; the generator could mark them[CLSCompliant(false)](or omit theSafeArraySubTypesarray, or emit the structs in a form the CLS rules accept) so consumers do not have to maintain a hand-written[CLSCompliant(false)]partial for each generated COM type.Actual
Each generated COM struct that has a
SafeArraySubTypes-decorated parameter emitsCS3016at every such call-site declaration. Consumers must either:CS3016globally (loses real diagnostic value for user code), or[CLSCompliant(false)] internal partial struct Foo { }declaration alongside an.editorconfigrule that suppresses the resultingCS3019noise.Environment
Microsoft.Windows.CsWin320.3.275net10.0;net472How the workaround looks in production
dotnet/msbuildcarries a dedicated file,src/Framework/Windows/Win32/GeneratedInteropClsCompliance.cs, that exists solely to attach[CLSCompliant(false)]to each generated COM struct that tripsCS3016, plus an.editorconfigrule that suppresses the resultingCS3019warnings for files under the CsWin32-generated namespaces.References
https://github.com/dotnet/msbuild/blob/main/src/Framework/Windows/Win32/GeneratedInteropClsCompliance.cs
[SuppressMessage]forCSxxxx:CLSCompliant(true) check for arrays as attribute arguments triggers on internal types dotnet/roslyn#68526
Suggested fix
Emit
[CLSCompliant(false)]on generated COM struct wrappers that contain non-CLS-compliant attribute arguments (or otherwise structure the generated[MarshalAs(...)]usage so the array argument is not required). Either change removes the need for consumers to author per-struct partial declarations.Sources
Drop these four files into a fresh folder and run
dotnet build.ClsCompliantSafeArray.csprojNativeMethods.txtNativeMethods.json{ "$schema": "https://aka.ms/CsWin32.schema.json", "allowMarshaling": false, "useSafeHandles": false }Program.csSketch of the fix
In the CsWin32 generator, when emitting a COM struct wrapper that contains any parameter decorated with
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubTypes = new[] { ... })](or any other attribute whose argument is an array), also emit[CLSCompliant(false)]on the struct declaration itself:Because the structs are
internal, this attribute is semantically a no-op for the assembly's CLS surface but suppressesCS3016at the source. The generator should not also need to suppressCS3019— that warning only fires when theattribute is applied to a member that the compiler thinks isn't externally visible and the assembly is CLS-compliant. Suppression of
CS3019for generated files remains the consumer's responsibility (e.g. via an.editorconfigrule scoped to the generated namespace), but is one-line and stable across new COM types.Alternative: emit
SafeArraySubTypesas a single-valued attribute argument when only one subtype is needed, or omit it entirely when the generator can prove it is unnecessary for blittable signatures (allowMarshaling: false).