diff --git a/AGENTS.md b/AGENTS.md
index 5305972..32e3c01 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -127,6 +127,7 @@ dotnet test DesignPatterns.slnx -c Release
| DP023 | 未注册工厂(Analyzer + CodeFix) |
| DP024 | 未注册 Handler(Analyzer + CodeFix) |
| DP025 | 未知注册表键字面量(Analyzer + CodeFix;Strategy/Factory `Get`/`TryGet`/`Create`/`TryCreate`) |
+| DP033 | 跨程序集重复 strategy key(Analyzer;多供应商宿主引用冲突) |
| DP026–DP031 | State 转换表(生成器;重复边、非法 enum、holder、孤立态 Info) |
| DP010–DP015 | CompositePart(生成器) |
| DP016–DP019 | Decorator(生成器) |
diff --git a/DesignPatterns.Analyzers/AnalyzerSymbolHelper.cs b/DesignPatterns.Analyzers/AnalyzerSymbolHelper.cs
index cd89184..e1f9b96 100644
--- a/DesignPatterns.Analyzers/AnalyzerSymbolHelper.cs
+++ b/DesignPatterns.Analyzers/AnalyzerSymbolHelper.cs
@@ -52,6 +52,13 @@ internal static bool ImplementsContract(INamedTypeSymbol typeSymbol, INamedTypeS
internal static INamedTypeSymbol? TryGetContractTypeFromAttribute(AttributeData attribute)
{
+ if (attribute.AttributeClass is INamedTypeSymbol attributeClass &&
+ attributeClass.TypeArguments.Length == 1 &&
+ attributeClass.TypeArguments[0] is INamedTypeSymbol typeArgumentContract)
+ {
+ return typeArgumentContract;
+ }
+
if (attribute.AttributeClass?.IsGenericType == true)
{
return attribute.AttributeClass.TypeArguments.Length == 1
diff --git a/DesignPatterns.Analyzers/CrossAssemblyRegistryKeyAnalyzer.cs b/DesignPatterns.Analyzers/CrossAssemblyRegistryKeyAnalyzer.cs
new file mode 100644
index 0000000..5970732
--- /dev/null
+++ b/DesignPatterns.Analyzers/CrossAssemblyRegistryKeyAnalyzer.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using DesignPatterns.Diagnostics;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace DesignPatterns.Analyzers;
+
+///
+/// Reports duplicate strategy keys for the same contract when multiple referenced provider assemblies are in the compilation.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class CrossAssemblyRegistryKeyAnalyzer : DiagnosticAnalyzer
+{
+ private static readonly DiagnosticDescriptor Rule =
+ DesignPatternsDiagnosticDescriptors.PluginRegistryDuplicateKeyAcrossAssemblies;
+
+ ///
+ public override ImmutableArray SupportedDiagnostics =>
+ ImmutableArray.Create(Rule);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+ context.RegisterCompilationAction(AnalyzeCompilation);
+ }
+
+ private static void AnalyzeCompilation(CompilationAnalysisContext context)
+ {
+ var registrations = CollectStrategyRegistrations(context.Compilation);
+ if (registrations.IsEmpty)
+ {
+ return;
+ }
+
+ ReportDuplicates(context, registrations);
+ }
+
+ private static void ReportDuplicates(
+ CompilationAnalysisContext context,
+ ImmutableArray registrations)
+ {
+ foreach (var group in registrations.GroupBy(
+ static registration => (
+ Contract: GetContractIdentity(registration.Contract),
+ registration.Key),
+ EqualityComparer<(string Contract, string Key)>.Default))
+ {
+ var assemblyNames = string.Join(
+ ", ",
+ group
+ .Select(static registration => registration.Assembly.Name ?? string.Empty)
+ .Distinct(StringComparer.Ordinal)
+ .OrderBy(static name => name, StringComparer.Ordinal));
+
+ if (group.Select(static registration => registration.Assembly).Distinct(SymbolEqualityComparer.Default).Count() <= 1)
+ {
+ continue;
+ }
+
+ foreach (var registration in group)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Rule,
+ registration.Location,
+ group.Key.Key,
+ group.Key.Contract,
+ assemblyNames));
+ }
+ }
+ }
+
+ private static string GetContractIdentity(INamedTypeSymbol contract) =>
+ contract.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+ private static ImmutableArray CollectStrategyRegistrations(Compilation compilation)
+ {
+ var builder = ImmutableArray.CreateBuilder();
+
+ foreach (var assembly in AnalyzerSymbolHelper.GetAssembliesInCompilation(compilation))
+ {
+ foreach (var typeSymbol in AnalyzerSymbolHelper.GetAllTypes(assembly.GlobalNamespace))
+ {
+ foreach (var attribute in typeSymbol.GetAttributes())
+ {
+ if (!StrategyAnalysisConstants.IsRegisterStrategyAttribute(attribute.AttributeClass))
+ {
+ continue;
+ }
+
+ var contract = AnalyzerSymbolHelper.TryGetContractTypeFromAttribute(attribute);
+ var key = AnalyzerSymbolHelper.TryGetKeyFromAttribute(attribute);
+ if (contract is null || key is null)
+ {
+ continue;
+ }
+
+ var location = attribute.ApplicationSyntaxReference?.GetSyntax().GetLocation()
+ ?? typeSymbol.Locations.FirstOrDefault()
+ ?? Location.None;
+
+ builder.Add(new StrategyRegistration(contract, key, assembly, location));
+ }
+ }
+ }
+
+ return builder.ToImmutable();
+ }
+
+ private sealed class StrategyRegistration
+ {
+ public StrategyRegistration(
+ INamedTypeSymbol contract,
+ string key,
+ IAssemblySymbol assembly,
+ Location location)
+ {
+ Contract = contract;
+ Key = key;
+ Assembly = assembly;
+ Location = location;
+ }
+
+ public INamedTypeSymbol Contract { get; }
+
+ public string Key { get; }
+
+ public IAssemblySymbol Assembly { get; }
+
+ public Location Location { get; }
+ }
+}
diff --git a/DesignPatterns.Analyzers/StrategyAnalysisConstants.cs b/DesignPatterns.Analyzers/StrategyAnalysisConstants.cs
index 5cf40c7..0b9a263 100644
--- a/DesignPatterns.Analyzers/StrategyAnalysisConstants.cs
+++ b/DesignPatterns.Analyzers/StrategyAnalysisConstants.cs
@@ -1,7 +1,23 @@
+using Microsoft.CodeAnalysis;
+
namespace DesignPatterns.Analyzers;
internal static class StrategyAnalysisConstants
{
internal const string RegisterStrategyMetadataName = "DesignPatterns.Behavioral.RegisterStrategyAttribute";
internal const string RegisterStrategyGenericMetadataName = "DesignPatterns.Behavioral.RegisterStrategyAttribute`1";
+
+ internal static bool IsRegisterStrategyAttribute(INamedTypeSymbol? attributeClass)
+ {
+ if (attributeClass is null)
+ {
+ return false;
+ }
+
+ return attributeClass.OriginalDefinition.MetadataName switch
+ {
+ "RegisterStrategyAttribute" or "RegisterStrategyAttribute`1" => true,
+ _ => false,
+ };
+ }
}
diff --git a/DesignPatterns.Diagnostics/DesignPatternsDiagnosticDescriptors.cs b/DesignPatterns.Diagnostics/DesignPatternsDiagnosticDescriptors.cs
index a348a12..f3412e6 100644
--- a/DesignPatterns.Diagnostics/DesignPatternsDiagnosticDescriptors.cs
+++ b/DesignPatterns.Diagnostics/DesignPatternsDiagnosticDescriptors.cs
@@ -223,6 +223,14 @@ public static class DesignPatternsDiagnosticDescriptors
DiagnosticSeverity.Info,
AnalyzerCategory);
+ public static DiagnosticDescriptor PluginRegistryDuplicateKeyAcrossAssemblies { get; } = Create(
+ DiagnosticIds.PluginRegistryDuplicateKeyAcrossAssemblies,
+ "Duplicate registry key across assemblies",
+ "Strategy key '{0}' for contract '{1}' is registered in multiple assemblies ({2}). Use unique keys per provider assembly or reference only one provider per contract dimension.",
+ "Plugin provider assemblies must not register the same strategy key for the same contract when both are referenced by the host.",
+ DiagnosticSeverity.Error,
+ AnalyzerCategory);
+
// State transition table (DP026–DP031)
public static DiagnosticDescriptor StateTransitionDuplicateEdge { get; } = Create(
diff --git a/DesignPatterns.Diagnostics/DiagnosticIds.cs b/DesignPatterns.Diagnostics/DiagnosticIds.cs
index 4dae704..58b6c37 100644
--- a/DesignPatterns.Diagnostics/DiagnosticIds.cs
+++ b/DesignPatterns.Diagnostics/DiagnosticIds.cs
@@ -30,6 +30,7 @@ public static class DiagnosticIds
public const string RegisterFactoryUnregisteredImplementation = "DP023";
public const string HandlerOrderUnregisteredImplementation = "DP024";
public const string RegistryKeyNotRegistered = "DP025";
+ public const string PluginRegistryDuplicateKeyAcrossAssemblies = "DP033";
public const string StateTransitionDuplicateEdge = "DP026";
public const string StateTransitionInvalidStateMember = "DP027";
public const string StateTransitionInvalidTriggerMember = "DP028";
diff --git a/DesignPatterns.SourceGenerators/AnalyzerReleases.Unshipped.md b/DesignPatterns.SourceGenerators/AnalyzerReleases.Unshipped.md
index 99887d3..4975710 100644
--- a/DesignPatterns.SourceGenerators/AnalyzerReleases.Unshipped.md
+++ b/DesignPatterns.SourceGenerators/AnalyzerReleases.Unshipped.md
@@ -27,6 +27,7 @@ DP006 | DesignPatterns.Analyzers | Info | Strategy implementation missing
DP023 | DesignPatterns.Analyzers | Info | Factory implementation missing RegisterFactory attribute
DP024 | DesignPatterns.Analyzers | Info | Handler implementation missing HandlerOrder attribute
DP025 | DesignPatterns.Analyzers | Info | Registry key is not registered for contract
+DP033 | DesignPatterns.Analyzers | Error | Duplicate strategy key across referenced provider assemblies
DP026 | DesignPatterns.Generators | Error | Duplicate state transition edge
DP027 | DesignPatterns.Generators | Error | Transition state is not a declared enum member
DP028 | DesignPatterns.Generators | Error | Transition trigger is not a declared enum member
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
index 394f8a3..f588b63 100644
--- a/docs/DEVELOPMENT.md
+++ b/docs/DEVELOPMENT.md
@@ -156,7 +156,7 @@ Nuke `UnitTest` 会依次运行 `tests/` 下四个测试项目,并将 TRX 写
| 单元 | `tests/DesignPatterns.Tests` | 运行时 API、特性校验 |
| 集成 | `tests/DesignPatterns.Tests/Integration/` | 生成器产出 → 运行时(Chain/Strategy/Composite/Singleton/Decorator/Factory/EventAggregator 单元测试在 Behavioral) |
| 快照 | `tests/DesignPatterns.SourceGenerators.Tests` | 生成源码与 DP 诊断(Verify) |
-| Analyzer | `tests/DesignPatterns.Analyzers.Tests` | DP006、DP023、DP024、DP025 与 CodeFix(Strategy/Factory/Handler/Registry key) |
+| Analyzer | `tests/DesignPatterns.Analyzers.Tests` | DP006、DP023、DP024、DP025、DP033 与 CodeFix(Strategy/Factory/Handler/Registry key) |
| DI 扩展 | `tests/DesignPatterns.Extensions.DependencyInjection.Tests` | DI 注册与解析 |
| AppSettings 扩展 | `tests/DesignPatterns.Extensions.AppSettings.Tests`(net8.0)、`tests/DesignPatterns.Extensions.AppSettings.Tests.Net48`(net48 编译) | AppSettings 桥接 |
| Configuration 扩展 | `tests/DesignPatterns.Extensions.Configuration.Tests`(net8.0)、`tests/DesignPatterns.Extensions.Configuration.Tests.Net48`(net48 编译) | `IConfiguration` 桥接 |
diff --git a/docs/PluginAssemblies.md b/docs/PluginAssemblies.md
index eb82bb4..8a9f181 100644
--- a/docs/PluginAssemblies.md
+++ b/docs/PluginAssemblies.md
@@ -119,6 +119,8 @@ FCErrorRegistry.RegisterAutofac(builder);
v1 **不**提供 `[RegisterStrategyBundle]`;若样板过多再评估(见 #108)。
+同一实现需在 Autofac 中暴露多个接口时,在模块中手动补充 `builder.RegisterType().As().As()`;仅当样例摩擦证明有必要时再评估生成器 `AdditionalContracts` 参数。
+
---
## 6. 配置选型
@@ -138,7 +140,8 @@ v1 **不**提供 `[RegisterStrategyBundle]`;若样板过多再评估(见 #10
|----|------|
| DP003 | 同一编译单元内重复 key(已有) |
| DP025 | 未知字面量 key(已有) |
-| **DP033** | 跨程序集重复 key 合并 — **暂不实现**:`partial` 无法跨程序集,重复 key 表现为多个同名 Registry 类型或宿主歧义,应在 **引用图 / 部署** 层保证单供应商维度 |
+| **DP033** | 宿主同时引用多个供应商程序集时,**同一契约**出现**相同 strategy key**(Analyzer Error) |
+| **DP034** | 供应商已引用但 key 未出现在样例/配置文档 — 可选 Info,**未实现** |
---
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 0de29a0..10aee1c 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -19,7 +19,7 @@
| Factory | `{Contract}Keys`、`{Contract}Registry` |
| State | `{StateEnum}TransitionTable`、partial `{Holder}` 便捷方法 |
-新增生成器必须沿用此命名风格(详见 [Decorator.md](Decorator.md))。诊断 ID 续接现有区段,下一个可用 ID 为 **DP032**。
+新增生成器必须沿用此命名风格(详见 [Decorator.md](Decorator.md))。诊断 ID 续接现有区段,下一个可用 ID 为 **DP034**(DP032 预留给 App.config 静态分析,**未实现**;DP033 为跨程序集重复 strategy key)。
---
diff --git a/tests/DesignPatterns.Analyzers.Tests/CrossAssemblyRegistryKeyAnalyzerTests.cs b/tests/DesignPatterns.Analyzers.Tests/CrossAssemblyRegistryKeyAnalyzerTests.cs
new file mode 100644
index 0000000..b463022
--- /dev/null
+++ b/tests/DesignPatterns.Analyzers.Tests/CrossAssemblyRegistryKeyAnalyzerTests.cs
@@ -0,0 +1,213 @@
+using DesignPatterns.Analyzers;
+
+namespace DesignPatterns.Analyzers.Tests;
+
+public sealed class CrossAssemblyRegistryKeyAnalyzerTests
+{
+ private const string HostSource = """
+ namespace Plugin.Host;
+
+ public static class Program
+ {
+ public static void Main()
+ {
+ }
+ }
+ """;
+
+ [Fact]
+ public async Task ReportsDp033WhenSameStrategyKeyExistsInTwoReferencedAssemblies()
+ {
+ const string alphaProviderSource = """
+ using DesignPatterns.Behavioral;
+
+ namespace Plugin.Contracts
+ {
+ public interface ICardMotion
+ {
+ string Name { get; }
+ }
+ }
+
+ namespace Plugin.Providers.Alpha
+ {
+ using Plugin.Contracts;
+
+ [RegisterStrategy("alpha")]
+ public sealed class AlphaCard : ICardMotion
+ {
+ public string Name => "alpha";
+ }
+ }
+ """;
+
+ const string betaProviderSource = """
+ using DesignPatterns.Behavioral;
+
+ namespace Plugin.Contracts
+ {
+ public interface ICardMotion
+ {
+ string Name { get; }
+ }
+ }
+
+ namespace Plugin.Providers.Beta
+ {
+ using Plugin.Contracts;
+
+ [RegisterStrategy("alpha")]
+ public sealed class BetaCard : ICardMotion
+ {
+ public string Name => "alpha-beta";
+ }
+ }
+ """;
+
+ var diagnostics = await AnalyzerTestContext.RunAnalyzersWithTwoReferencedAssembliesAndHostAsync(
+ alphaProviderSource,
+ betaProviderSource,
+ HostSource,
+ new CrossAssemblyRegistryKeyAnalyzer());
+
+ Assert.Equal(2, diagnostics.Count(diagnostic => diagnostic.Id == "DP033"));
+ Assert.All(
+ diagnostics.Where(diagnostic => diagnostic.Id == "DP033"),
+ diagnostic =>
+ {
+ Assert.Contains("alpha", diagnostic.GetMessage(), StringComparison.Ordinal);
+ Assert.Contains("ICardMotion", diagnostic.GetMessage(), StringComparison.Ordinal);
+ Assert.Contains("ProviderAlpha", diagnostic.GetMessage(), StringComparison.Ordinal);
+ Assert.Contains("ProviderBeta", diagnostic.GetMessage(), StringComparison.Ordinal);
+ });
+ }
+
+ [Fact]
+ public async Task DoesNotReportDp033WhenReferencedAssembliesUseDistinctKeys()
+ {
+ const string alphaProviderSource = """
+ using DesignPatterns.Behavioral;
+
+ namespace Plugin.Contracts
+ {
+ public interface ICardMotion
+ {
+ string Name { get; }
+ }
+ }
+
+ namespace Plugin.Providers.Alpha
+ {
+ using Plugin.Contracts;
+
+ [RegisterStrategy("alpha")]
+ public sealed class AlphaCard : ICardMotion
+ {
+ public string Name => "alpha";
+ }
+ }
+ """;
+
+ const string betaProviderSource = """
+ using DesignPatterns.Behavioral;
+
+ namespace Plugin.Contracts
+ {
+ public interface ICardMotion
+ {
+ string Name { get; }
+ }
+ }
+
+ namespace Plugin.Providers.Beta
+ {
+ using Plugin.Contracts;
+
+ [RegisterStrategy("beta")]
+ public sealed class BetaCard : ICardMotion
+ {
+ public string Name => "beta";
+ }
+ }
+ """;
+
+ var diagnostics = await AnalyzerTestContext.RunAnalyzersWithTwoReferencedAssembliesAndHostAsync(
+ alphaProviderSource,
+ betaProviderSource,
+ HostSource,
+ new CrossAssemblyRegistryKeyAnalyzer());
+
+ Assert.DoesNotContain(diagnostics, diagnostic => diagnostic.Id == "DP033");
+ }
+
+ [Fact]
+ public async Task DoesNotReportDp033WhenDuplicateKeyExistsInSingleAssembly()
+ {
+ const string source = """
+ using DesignPatterns.Behavioral;
+
+ namespace TestAssembly;
+
+ public interface ICardMotion
+ {
+ string Name { get; }
+ }
+
+ [RegisterStrategy("alpha")]
+ public sealed class AlphaCard : ICardMotion
+ {
+ public string Name => "alpha";
+ }
+
+ [RegisterStrategy("alpha")]
+ public sealed class BetaCard : ICardMotion
+ {
+ public string Name => "beta";
+ }
+ """;
+
+ var diagnostics = await AnalyzerTestContext.RunAnalyzersAsync(
+ source,
+ new CrossAssemblyRegistryKeyAnalyzer());
+
+ Assert.DoesNotContain(diagnostics, diagnostic => diagnostic.Id == "DP033");
+ }
+
+ [Fact]
+ public async Task DoesNotReportDp033WhenSameKeyIsUsedForDifferentContracts()
+ {
+ const string source = """
+ using DesignPatterns.Behavioral;
+
+ namespace TestAssembly;
+
+ public interface IControl
+ {
+ void Run();
+ }
+
+ public interface IErrorHandler
+ {
+ string Describe();
+ }
+
+ [RegisterStrategy("gamma")]
+ public sealed class GammaControl : IControl
+ {
+ public void Run() { }
+ }
+
+ [RegisterStrategy("gamma")]
+ public sealed class GammaError : IErrorHandler
+ {
+ public string Describe() => "gamma";
+ }
+ """;
+
+ var diagnostics = await AnalyzerTestContext.RunAnalyzersAsync(
+ source,
+ new CrossAssemblyRegistryKeyAnalyzer());
+
+ Assert.DoesNotContain(diagnostics, diagnostic => diagnostic.Id == "DP033");
+ }
+}
diff --git a/tests/DesignPatterns.Analyzers.Tests/DesignPatternsDiagnosticDescriptorsTests.cs b/tests/DesignPatterns.Analyzers.Tests/DesignPatternsDiagnosticDescriptorsTests.cs
index 51f7672..58de59b 100644
--- a/tests/DesignPatterns.Analyzers.Tests/DesignPatternsDiagnosticDescriptorsTests.cs
+++ b/tests/DesignPatterns.Analyzers.Tests/DesignPatternsDiagnosticDescriptorsTests.cs
@@ -10,6 +10,7 @@ public sealed class DesignPatternsDiagnosticDescriptorsTests
[InlineData("DP006")]
[InlineData("DP024")]
[InlineData("DP025")]
+ [InlineData("DP033")]
public void Help_link_uses_diagnostics_page_fragment(string diagnosticId)
{
var helpLink = DiagnosticHelpLinks.For(diagnosticId);
@@ -38,4 +39,15 @@ public void Registry_key_descriptor_exposes_help_link_and_description()
Assert.Equal(DiagnosticHelpLinks.For(descriptor.Id), descriptor.HelpLinkUri);
Assert.Contains("Registered keys", descriptor.MessageFormat.ToString());
}
+
+ [Fact]
+ public void Cross_assembly_registry_key_descriptor_exposes_help_link_and_description()
+ {
+ var descriptor = new CrossAssemblyRegistryKeyAnalyzer().SupportedDiagnostics[0];
+
+ Assert.Equal(DiagnosticIds.PluginRegistryDuplicateKeyAcrossAssemblies, descriptor.Id);
+ Assert.False(string.IsNullOrWhiteSpace(descriptor.Description.ToString()));
+ Assert.Equal(DiagnosticHelpLinks.For(descriptor.Id), descriptor.HelpLinkUri);
+ Assert.Contains("multiple assemblies", descriptor.MessageFormat.ToString());
+ }
}
diff --git a/tests/DesignPatterns.Analyzers.Tests/UnregisteredStrategyAnalyzerTests.cs b/tests/DesignPatterns.Analyzers.Tests/UnregisteredStrategyAnalyzerTests.cs
index 83e04b7..4524f2b 100644
--- a/tests/DesignPatterns.Analyzers.Tests/UnregisteredStrategyAnalyzerTests.cs
+++ b/tests/DesignPatterns.Analyzers.Tests/UnregisteredStrategyAnalyzerTests.cs
@@ -43,6 +43,22 @@ internal static async Task> RunAnalyzersWithReference
.ConfigureAwait(false);
}
+ internal static async Task> RunAnalyzersWithTwoReferencedAssembliesAndHostAsync(
+ string firstReferencedAssemblySource,
+ string secondReferencedAssemblySource,
+ string hostAssemblySource,
+ params DiagnosticAnalyzer[] analyzers)
+ {
+ var compilation = CreateCompilationWithTwoReferencedAssembliesAndHost(
+ firstReferencedAssemblySource,
+ secondReferencedAssemblySource,
+ hostAssemblySource);
+ return await compilation
+ .WithAnalyzers(ImmutableArray.Create(analyzers))
+ .GetAnalyzerDiagnosticsAsync()
+ .ConfigureAwait(false);
+ }
+
internal static async Task ApplyGeneratorCodeFixAsync(
string source,
string diagnosticId,
@@ -147,6 +163,59 @@ private static CSharpCompilation CreateCompilationWithReferencedAssembly(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
}
+ private static CSharpCompilation CreateCompilationWithTwoReferencedAssembliesAndHost(
+ string firstReferencedAssemblySource,
+ string secondReferencedAssemblySource,
+ string hostAssemblySource)
+ {
+ var parseOptions = new CSharpParseOptions(LanguageVersion.Latest);
+ var firstReference = EmitCompilationReference(
+ "ProviderAlpha",
+ firstReferencedAssemblySource,
+ "Alpha.cs",
+ parseOptions);
+ var secondReference = EmitCompilationReference(
+ "ProviderBeta",
+ secondReferencedAssemblySource,
+ "Beta.cs",
+ parseOptions);
+
+ var hostTree = CSharpSyntaxTree.ParseText(
+ hostAssemblySource,
+ parseOptions,
+ path: "Host.cs");
+ return CSharpCompilation.Create(
+ "Host",
+ new[] { hostTree },
+ References.Add(firstReference).Add(secondReference),
+ new CSharpCompilationOptions(OutputKind.ConsoleApplication));
+ }
+
+ private static MetadataReference EmitCompilationReference(
+ string assemblyName,
+ string source,
+ string fileName,
+ CSharpParseOptions parseOptions)
+ {
+ var tree = CSharpSyntaxTree.ParseText(source, parseOptions, path: fileName);
+ var compilation = CSharpCompilation.Create(
+ assemblyName,
+ new[] { tree },
+ References,
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ using var stream = new MemoryStream();
+ var emitResult = compilation.Emit(stream);
+ if (!emitResult.Success)
+ {
+ throw new InvalidOperationException(
+ string.Join(Environment.NewLine, emitResult.Diagnostics.Select(diagnostic => diagnostic.ToString())));
+ }
+
+ stream.Position = 0;
+ return MetadataReference.CreateFromStream(stream);
+ }
+
private static ImmutableArray CreateReferences()
{
var references = new List