Search code examples
c#.net-assemblyassembliessourcegenerators

Right way to generate shared code in source generators


I am working on a library that will use source generators to provide implementation for user defined types. Which type to generate the code for and how to generate it is provided using Attributes on the type to generate the implementation for. The generated type will use some delegates that will be the same in every generated type. In a project like this there seem to be 3 kinds of code that will be generated:

  • Code that defines the attributes that will later be used in the generator
  • Code that defines types that will be reused in the rest of the generated code
  • Code that defines actual types the user requested

From what I understood from source generator examples it is a common practice to define all types used in the user code by generating them rather than including them in own assembly.

I have some problems understanding how the generated code will behave in a multi-project solution. Suppose with have 2 assemblies A and B, where each needs my library to generate some source code. With my current implementation both assemblies will have definitions for needed attributes, delegates and partial user types.

My problem is, what happens when both assemblies have definitions for the same delegates in the same namespace(They should be in the same namespace since they are used in all generated types)? If the delegates are generated there is no compilation error, but if I define the same delegates in a different solution where they are not generated and are included in separate assemblies I get the error: CS0101 (The namespace 'MyNamespace' already contains a definition for 'MyDelegate').

This gets me to think if these types when generated in different assemblies will be interchangeable. I think my actual question is if I am generating my sources correctly. Is it right to generate everything the user assembly will need or should I provide some types in the actual assembly of my source generator library?

I am not sure if what I written above is understandable enough to provide an answer but I wouldn't know how to describe it better. If you need clarification about something please ask.


Solution

  • The code generated by source generators is "just" a code, it follows all the rules as the one you would write by yourself (for example it can't generate "unnamable" identifiers unlike the compiler). This means that you will need to tackle the problem with corresponding tools.

    If you take a look at the official Source Generators Cookbook and the example of using marker attributes INotifyPropertyChanged implementation then you will see that the default approach for such cases is to generate an internal code:

    [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
    sealed class AutoNotifyAttribute : Attribute
    {
        public AutoNotifyAttribute()
        {
        }
        public string PropertyName { get; set; }
    }
    

    This can be not usable in your case then you can consider an alternative approaches. If your depended code is not actually "dynamic" (i.e. the generated delegates are always the same) then you can move such code to a shared package/dll and add it with the package. For more detailed guidance for such cases I recommend you to check the options listed in the Solving the source generator 'marker attribute' problem article by Andrew Lock.

    They should be in the same namespace since they are used in all generated types

    But this namespace can differ per project for example. Either by adding some random ID to it or using the project root namespace (have not done that in my projects but it seems that this should be possible - for example see How to validate Projects property default namespace with Roslyn analyzer, maybe there are other options too)