Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ScanStatic extension method using C# Source Generators #129

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

david-driscoll
Copy link

@david-driscoll david-driscoll commented Sep 26, 2020

This is just an initial commit, I'm sure there are something things that could be done differently and better.

Basically source generation under the covers emits an extension method that looks a little like...

public static IServiceCollection ScanStatic(
    this IServiceCollection services,
    Action<ICompiledAssemblySelector> action,
    [CallerFilePathAttribute] string filePath = """",
    [CallerMemberName] string memberName = """",
    [CallerLineNumberAttribute] int lineNumber = 0
)
{
    return PopulateExtensions.Populate(services, RegistrationStrategy.Append, filePath, memberName, lineNumber);
}

PopulateExtensions is also emitted later based on the action that was sent to each ScanStatic method.

An example from the initial unit tests is like below. Basically we just piggy back off the fact that CallerXyz attributes are emitted at compile time, and we can get those values during source generation as well.

public static IServiceCollection Populate(IServiceCollection services, RegistrationStrategy strategy, string filePath, string memberName, int lineNumber)
{
    switch (lineNumber)
    {
        case 11:
            switch (filePath)
            {
                case ""Test1.cs"":
                    strategy.Apply(services, ServiceDescriptor.Describe(typeof(Service), typeof(Service), ServiceLifetime.Singleton));
                    strategy.Apply(services, ServiceDescriptor.Describe(typeof(IService), _ => _.GetRequiredService(typeof(Service)), ServiceLifetime.Singleton));
                    break;
                case ""Test2.cs"":
                    strategy.Apply(services, ServiceDescriptor.Describe(typeof(ServiceB), typeof(ServiceB), ServiceLifetime.Scoped));
                    strategy.Apply(services, ServiceDescriptor.Describe(typeof(IServiceB), _ => _.GetRequiredService(typeof(ServiceB)), ServiceLifetime.Scoped));
                    break;
            }

            break;
    }

    return services;
}

The I'm currently using the precedence of LineNumber -> FilePath -> MemberName instead of FilePath -> MemberName -> LineNumber

  1. Line number will largely be different between between other files.
  2. It's likely that ScanStatic will be called from the same file a few different times, requiring two jumps.
  3. It's unlikely (however possible) that you could have a method on the same line, same file with two different member names.
  • God forbid anyone code like void A(IServiceCollection services) { services.ScanStatic(z => z.FromAssemblies().AddClasses(); } void B(IServiceCollection services) { services.ScanStatic(z => z.FromAssemblies().AddClasses(); } all on one line.

Assuming all unique line numbers, then we only have to deal with one switch before we start adding services, hopefully this adds as little overhead as possible (compared to full assembly scanning anyway).

  • Review if the concept makes sense to @khellang
  • Needs more unit tests
  • Needs testing related to generic types
  • Need to add diagnostics, everything has to be static inside the action <T> or typeof()

Let me know what you think!

@khellang
Copy link
Owner

Thanks for this @david-driscoll! I need to take some time to look at this and I'll get back to you 😄

@khellang khellang self-requested a review September 28, 2020 07:29
@khellang khellang mentioned this pull request May 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants