はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】SourceGeneratorとInternalVisibleToを組み合わせるとCS0436という型競合が起こる問題の対処

はじめに

お久しぶりです。最近ブログ更新サボってましたが、不定期になりますがまた書いていきたいと思います。

今回はSourceGeneratorInternalsVisibleToを組み合わせたときに型競合(CS0436)が起こる問題について書きたいと思います。

概要

SourceGeneratorRegisterPostInitializationOutputで属性生成するのは定番の手法です。例えばCySharpさんのUnitGeneratorでもUnitOfAttributeを生成しています。

github.com

ただInternalVisiableToを組み合わせると型競合(CS0436)が起こってしまう問題が存在します。具体的には、以下のような構成にすると警告がでてきます。

<!-- SampleConsole.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net10.0</TargetFramework>
    </PropertyGroup>

    <!-- UnitGeneratorを利用している -->
    <ItemGroup>
        <PackageReference Include="UnitGenerator" Version="2.0.0">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>
    
    <!-- ClassLibrary1.csprojへ依存している。InternalVisibleToによりinternalにもアクセス可 -->
    <ItemGroup>
      <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
    </ItemGroup>
</Project>
<!-- ClassLibrary1.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net10.0</TargetFramework>
    </PropertyGroup>

    <!-- UnitGeneratorを利用している -->
    <ItemGroup>
      <PackageReference Include="UnitGenerator" Version="2.0.0">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
    </ItemGroup>

    <!-- SampleConsoleからClassLibrary1のinternalに対してアクセス可能にする -->
    <ItemGroup>
        <InternalsVisibleTo Include="SampleConsole" />
    </ItemGroup>
</Project>
# 実際にでてくる警告
$ dotnet build
'C:\...\UnitOfAttribute.g.cs' の型 'UnitOfAttribute<T>' は、'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' のインポートされた型 'UnitOfAttribute<T>' と競合しています。'C:\...\UnitOfAttribute.g.cs' で定義された型を使用しています。

つまりはどちらのアセンブリにも同名のinternalな属性が定義されていて、InternalVisibleToを利用すると競合を起こすということです。

解決策

.Net10以降

この問題に対してちゃんと対策をしてくれています。それが[Embedded]属性です。

github.com
API Proposal: `IncrementalGeneratorPostInitializationContext.AddEmbeddedAttributeDefinition` · Issue #76584 · dotnet/roslyn · GitHub

具体的には

  • 生成する属性に対して[global::Microsoft.CodeAnalysis.Embedded]を付与する
  • RegisterPostInitializationOutputの中でAddEmbeddedAttributeDefinition()を実行する

をすることで解決できます。

context.RegisterPostInitializationOutput(static postInitializationContext =>
    postInitializationContext.AddEmbeddedAttributeDefinition();
    postInitializationContext.AddSource("myGeneratedFile.cs", SourceText.From("""
      using System;
      using Microsoft.CodeAnalysis;
      namespace GeneratedNamespace
      {
          [AttributeUsage(AttributeTargets.Method), Embedded]
          internal sealed class GeneratedAttribute : Attribute
          {
          }
      }
      """, Encoding.UTF8)));

.Net10以前

属性を共有DLLとして定義して、それを参照するようにするという手法が挙げられます。例えばNetEscapades.EnumGeneratorsというOSSでは、NetEscapades.EnumGenerators.Attributesを用意しています。

github.com

具体的な手法は以下の記事でまとめられているのでチェックしてみてください。

andrewlock.net

Unityの場合

現在(2026/3/25)、Unityは最新でもMicrosoft.CodeAnalysis.CSharp v4.0.4使ってて、4.13.0以上(4.14.0かも?)に上げてもらわないと先ほどの手法を利用することができないはずです。

www.nowsprinting.com

learn.microsoft.com


ですのでUnity対応を考えると、結局使うことができずcsc.rspやpragmaで暫定対応するしかないという...ということになりつらいなぁという感じでした。