Performance in .NET – Part 4

Introduction

This is my fourth post on performance in the .NET world. See the first one on object instantiation here and the second on property copying here and the third here. This time I’m going to talk about collections, but focusing on the performance side.This time, I’ll be talking about value types.

Value Types versus Reference Types

Value types – structs and enums – are always allocated in the stack, as opposed to reference types – classes – , which are allocated in the heap. This means that value types are automatically released from memory when they go out of scope – end of the block/method where they are declared, or the class is garbage collected, which is more rare. Value types are thus cheaper to create and do not need to be checked by the garbage collector.

Some aspects, though, need attention.

Instance Comparison

When you compare two value types using the Equals method, if there is no override for it, the value type is compared byte by byte. This is, as you can image, pretty inefficient. Is is recommended that you implement your own Equals (and GetHashCode too) and also that you implement IEquatable<T> interface for comparing two instances of your value type without incurring into boxing and unboxing.

Usage in Lists

Storing value types in array-based lists that permit reordering (random insertions and deletions), such as List<T> is painful, because of the items need to be copied, and copying for value types means byte by byte copying. Avoid if possible.

Usage in Arrays

Value types are great for usage in arrays, because a value type has no object header, so it’s size in memory is very small. The size of the array is therefore small when compared to the same array of reference types.

Conclusion

Do use value types as much as possible, but stay aware of the problems. As always, looking forward to hearing your thoughts. I’ll be back for more.

C# 8.0 and .NET Core 3.0 – Modern Cross-Platform Development – Fourth Edition Review

C# 8.0 and .NET Core 3.0 – Modern Cross-Platform Development

This is a book written by Mark J. Price for Packt Publishing. I must say that I don’t know Mark, but I was asked by Pack to review this book, which I gladly did!

The topics covered in this book are vast, so it should be no surprise the size of it: more than 800 pages. It spawns across 21 chapters, each of them ends with some exercises. It talks about pretty much everything .NET, as you can see:

Chapter 1

Introduction to Visual Studio Code and Visual Studio 2019 for Windows and Mac. Presents some useful extensions for Visual Studio Code. Talks a bit about the .NET Framework and its branches and offspring, Xamarin, Mono and .NET Core. Also explains the intermediate language that .NET compiles to and how to produce native code from .NET. Also shows how to use Visual Studio Code to get code from GitHub and build console apps. Finally, it gives some pointers on how to get support from the Internet.

Chapter 2

Presents all the C# versions from 1 until 8, the last one to date. Shows how to enable a specific version on a .NET project. Gives a an overview of the unchanging C# language features, like variables, data types, reference and value types, and presents a simple example on how to get input from the user.

Chapter 3

Expands on the C# language started on the previous chapter introducing branching, conditions, pattern matching, assignment, operators, loops and casts.

Chapter 4

Introduces functions, how to debug and unit test them.

Chapter 5

Explains Object-Oriented Programming with C#. How references to assemblies and namespaces work. Field and property modifiers. How to use the return values of functions, including tuples. Method overloading, optional parameters. Partial classes. Properties with indexers and different levels of access.

Chapter 6

Talks about interfaces and type inheritance. Explains events and delegates. Generic types. Reference and value types. The dispose pattern. Member overriding and hiding. Preventing inheritance. Polymorphism. Casting. Extension methods.

Chapter 7

Packaging .NET components in assemblies and NuGet packages. The .NET Standard and .NET Core. Publishing applications. Decompiling assemblies. Publishing to NuGet. Porting to .NET Core.

Chapter 8

Common .NET types and operations: numbers, string, regular expressions, collections, spans, indexes, ranges, network resources, types, attributes. Internationalization.

Chapter 9

Files, file streams and serialization. Working with the filesystem. Text encoding. XML and JSON serialisation and compression.

Chapter 10

Data encryption and decryption. Data hashing and signing. Random number generation. User authentication and authorisation.

Chapter 11

Database programming with Entity Framework Core.

Chapter 12

Using LINQ. Custom LINQ methods. LINQ to XML. Parallel LINQ.

Chapter 13

Performance monitoring. Tasks. Synchronizing access to shared resources. async and await.

Chapter 14

ASP.NET Core web applications. SignalR. Blazor.

Chapter 15

ASP.NET Core Razor Pages. Using EF Core with ASP.NET Core. Razor Class Libraries.

Chapter 16

ASP.NET Core MVC.

Chapter 17

Using Content Management Systems (Piranha CMS).

Chapter 18

ASP.NET Core Web API. Swagger and Open API. Health checks. WCF and gRPC are mentioned briefly.

Chapter 19

Machine learning with ML.NET.

Chapter 20

Windows Forms apps. Windows Presentation Foundation apps with .NET Core and Windows Compatibility Pack. The XAML Standard. Modern Windows apps.

Chapter 21

Using Xamarin for building cross-platform mobile apps. Calling web services.

Conclusion

As you can see, this is a lot, and goes from the plain C# language to machine learning and Blazor. I’d say that some topics, such as Content Management Systems, could have been dropped, but other than that, pretty much everything that a developer longing to learn .NET could wish for is here. A great deal of ASP.NET Core, which is good, as it is for sure the strong part of .NET Core. So, if you’re one such developer, this is one book that you may want to get, you won’t feel disappointed! Definitely a good value for money!

Now Reading: C# 8.0 and .NET Core 3.0 – Modern Cross-Platform Development – Fourth Edition

Update: see the review here.

I am now reading C# 8.0 and .NET Core 3.0 – Modern Cross-Platform Development – Fourth Edition, by Mark J. Price, from Packt Publishing. Expect a review from it very soon.

C# 8.0 and .NET Core 3.0 - Modern Cross-Platform Development - Fourth Edition

@MarkJPrice @RavitJain @PacktPub

TypeScript for C# and .NET Core Developers Review

I finished reading Hands-On TypeScript for C# and .NET Core Developers by Francesco Abbruzzese (@f_abbruzzese) for Packt Publishing. As the name states, it is about TypeScript (and JavaScript) and also very much about Angular.

The book is structured like this:

  1. First chapter explains what is TypeScript (version 2.8.3), how to install it using NPM or the SDK, how to create your first project, basic configuration options, the type system and syntax; at all times, it relates the TypeScript syntax with the recent ECMAScript versions, of which TypeScript is a superset
  2. The second one talks about type declaration, including interfaces, classes, unions, tuples, arrays and so on. It also covers operations over types, such as destructuring and spreads. Finally, it presents functions in TypeScript, how to mimic overloading and have optional arguments
  3. Chapter 3 covers DOM manipulation. This is probably something that seasoned web/JavaScript developers are quite familiar with, but, most importantly, it also introduces declaration files
  4. In chapter 4 we learn how to make effective use of classes and interfaces, declare visibility levels and modifiers, and how type compatibility works
  5. Generics is the topic of chapter 5, how to declare generic types and functions and how to enforce constraints
  6. This chapter talks about modules and namespaces, the different ways by which we can resolve and load modules, and also about TypeScript type declarations, which are used to call untyped JavaScript code
  7. Here we learn how to integrate the WebPack bundler and minifier with ASP.NET Core and how we can enable debugging of the source code in Visual Studio
  8. A very important chapter, here we get an overview on how to write reusable libraries and make them available on NPM. Testing goes together with reusability, so we learn how to use Jasmine to unit test our code
  9. In this chapter we learn about symbols, the TypeScript equivalent to decorators in Aspect-Oriented Programming, generator functions and iterators and also promises, used for asynchronous invocations. Not exactly related, but it also covers the fetch API, used for modern AJAX-style interactions
  10. Chapter 10 presents the ASP.NET Core project template for Angular using TypeScript, describes the Angular architecture and key concepts
  11. This chapter teaches us how to interact with form fields, including validations, how the Angular lifecycle works and how to achieve two-way communication, data binding and piping between components
  12. This one presents some advanced Angular features, such as custom attribute and structural directives, which are then used to create animations by doing DOM manipulation
  13. Lastly, this chapter explains what is dependency injection and its benefits and how we can leverage it with TypeScript and Angular. It also describes how we can localize messages and use HTTP-related modules for interaction with external services. Most importantly, it presents the basis of Angular routing and navigation, a must-have for any complex application, and ends with an overview of testing for

At the end of each chapter there is a summary which highlights the key aspects that were introduced in it, poses 10 questions, which are answered in the Assessments chapter.

The book covers TypeScript 2.8.3, which is relatively old by now, but given the pace that TypeScript versions come out, it can hardly surprise us. Some new stuff in TS is missing, of course, but I guess this will always happen. It is essentially a book about TypeScript and Angular, but of course also covers Node.js and, obviously, JavaScript itself. .NET Core here is discussed as leverage to deploy and compile client-side code. The book is quite comprehensive and was actually an interesting read and I definitely learnt a lot.

The source code can be found in GitHub here: https://github.com/PacktPublishing/Hands-On-TypeScript-for-CSharp-and-.NET-Core-Developers.

If you got interested, please go get if from the Packt Publishing site: https://www.packtpub.com/application-development/hands-typescript-c-and-net-core-developers.

Now Reading: Hands-On TypeScript for C# and .NET Core Developers

I started reading a book by my MVP colleague Francesco Abbruzzese (@f_abbruzzese) on C# and TypeScript: Hands-On TypeScript for C# and .NET Core Developers. So far, it seems an interesting reading! Will write a review about it here, once I finish reading it. In case you want to know more, go get if from the Packt Publishing site: https://www.packtpub.com/application-development/hands-typescript-c-and-net-core-developers.

Succinctly Series Readers Awards

image

My e-book Entity Framework Core Succinctly was silver winner on the Succinctly Series Readers Awards!

Many thanks to all who voted for it! And congratulations to Joseph D. Booth for winning the gold award for his Natural Language Processing Succinctly and to Alessando Del Sole (@progalex) for his bronze award for Xamarin.Forms Succinctly!

https://www.syncfusion.com/awards/succinctlyseries/2018succinctlyreadersawards

Performance in .NET – Part 3

Introduction

This post is part of a series on performance in .NET. See the first one on object instantiation here and the second on property copying here. This time I’m going to talk about collections, but focusing on the performance side.

Collections

Back in 2009 (!) I wrote a blog post, which I updated a couple of times, about .NET collection types. Essentially, my point was – is – that you should pick the right collection for the job that you have at hands. This post is still up to date.

How many of us don’t by default just choose List<T> when there is need for a general-purpose collection? I certainly do, at times… Well, it turns out that this may or may not be what we need. Let me give a few examples.

List<T> is an array-based collection, which means that it is probably the best if you are going to iterate through items one at a time, sequentially, but it is not so good if you want to remove items from a random position other than the list’s end, because this causes a whole new array to be instantiated in memory, and all items (except the one that you wish to remove, of course) from the original list to be copied into the new one. It’s the same problem with random inserts at any given position, other than the end, and only if the initial capacity isn’t exceeded.

For operations where random inserts and deletes are required, LinkedList<T> is a much better choice as it does not require the instantiation of new arrays and the memory copy. It does that, however, at the expense of a slightly poorer performance in list traversal.

What about duplicates? With the previous list implementations, if we don’t want to allow duplicates, we need to check one by one, which is a PITA. Luckily, the .NET BCL has an implementation of a mathematical set which automatically excludes duplicates. One implementation is HashSet<T>, which is an indexed collection that uses each object’s GetHashCode method to figure out if the object already exists in the collection; needless to say, this method must be properly implemented.

Talking about indexed collections, if we want to be able to rapidly get to one element – or a number of elements – by some key, .NET has that as well: Dictionary<TKey, TValue> for storing a single value per unique key. The key can be of whatever type we want. This one also offers good performance when it comes to retrieving, adding, removing or modifying a single item.

Then we have LIFO and FIFO implementations in the form of the Stack<T> and Queue<T> types, which are optimized for adding items at the top or the bottom of the list, respectively, and don’t even allow other kinds of operations other than traversal. Internally they also use a linked-list approach.

Bits have their own specialized collections, BitArray and BitVector32. The latter only works with up to 32 bits and it probably provides the fastest performance of the two, according to Microsoft:

BitVector32 is more efficient than BitArray for Boolean values and small integers that are used internally. A BitArray can grow indefinitely as needed, but it has the memory and performance overhead that a class instance requires. In contrast, a BitVector32 uses only 32 bits.

Finally, Microsoft makes available thread-safe collections that are thread-safe in nature and therefore do not need any thread synchronization mechanisms, which makes them faster than if we had to roll out our own thread synchronization. They include thread-safe dictionaries (ConcurrentDictionary<TKey, TValue>), queues (ConcurrentQueue<T>), stacks (ConcurrentStack<T>) and general-purpose lists (BlockingCollection<T>).

Conclusion

I didn’t go through all the collection classes available, for that you can refer to my previous post.

The point I want to make with this post is:

  • Pick the right collection for the job; use the 80-20 rule and try to understand what the most common usage for your collection will be; do not just go blindly with List<T> or similar
  • Always expose collections publicly though interfaces or base classes that only show the bare minimum required, so that you can swap out the implementation should you need to
  • And, of course, measure your usage so that you can make opinionated decisions.

If performance is not an issue, by all means, forget about this and keep on doing what you are already doing and works for you.

Java vs C# – Part 3

Introduction

This is the third in a series of posts about the similarities and differences between C# (and .NET, to some extent) and Java. You can find the first one here and the second one here.

Again, with this I do not intent to demonstrate that one is superior to the other – totally – as I really like both platforms, even though I work mostly with C# and .NET. This is merely an exercise to show these differences and maybe it can be useful to someone who is learning one or the other. If you find something that you think is wrong, please let me know!

Keyword Usage

In Java, it is not possible to use reserved words, or keywords, such as class, public, etc, as variables, parameters or field names. In C# you can if you prefix it with @:. For example:

var @class = “My Class”;

Object Class

The Object class is the root of both type hierarchies in both Java and .NET. The two are pretty similar, with some remarkable exceptions. All of the following have exactly identical behavior:

Java .NET
clone MemberwiseClone
getClass GetType
equals Equals
hashCode GetHashCode
finalize Finalize
toString ToString

.NET does not offer methods corresponding to notify, notifyAll or wait. On the other hand, Java does not offer a method like ReferenceEquals.

Tuples

Recent versions of C# lets us return tuples, which are sets of values combined together ad hoc, but not inside a type definition. For example, should you wish to return a coordinate from a method, you could do this:

(double x, double y) GetCoordinates()

{

double x = …;

double y = …;

return (x, y);

}

It is also possible to “deconstruct” a class into a tuple, by providing a proper Deconstruct method:

public class Coordinate

{

public void Deconstruct(out double x, out double y)

{

x = …;

y = …;

}

}

Coordinate coord = …;

(double x, double y) = coord;

You can have as many Deconstruct methods you like, provided their signatures are different. For now, at least, Java is still lacking this functionality.

nameof

C# lets us use the nameof keyword to obtain a strongly-typed name of a class, method, property, field or parameter. It is very useful because it is refactor-friendly: should you change the name of the target, you also change the value that is being assigned. An example:

var className = nameof(MyClass); //”MyClass”

var fieldName = nameof(MyClass.MyField); //”MyField”

Mind you, only the “final” piece is returned, for example, if you use nameof with a fully qualified type name, you’ll only get the type name.

Friend Assemblies

As you know, internal classes and their methods are not available outside the current assembly/namespace. In C#/.NET, however, we can make these internals available to other assemblies by applying the InternalsVisibleToAttribute attribute to the assembly that we want to make available. These are called friend assemblies. For example:

[assembly:InternalsVisibleTo(“My.Assembly”)]

Async/Await

C# 5 introduced a new asynchronous programming model around the async and await keywords. I won’t go into the details of it but essentially it simplifies asynchronous programming a lot, preventing the usage of a lot of boilerplate code. It goes like this:

async Task<int> Compute(int a, int b) { … }

var result = await Compute(1, 2);

Import Methods

In C# we can import public static methods from public classes, which means, we can use them without prefacing them with the name of the class. The syntax is like this:

using static System.Environment;

and the usage:

var path = GetEnvironmentVariable(“PATH”);

Local Functions

.NET allows us to define local functions, that is, functions that exist only in the scope of methods. They are similar to lambda functions, with some remarkable differences, among which:

  • They can have attributes applied to its parameters
  • They can have ref, out and other kind of parameters
  • They can be asynchronous

An example:

void SomeMethod()

{

int sum(int a, int b) => a + b;

var x = sum(1, 2);

}

The closest that Java offers is anonymous classes, which are actually pretty cool, IMO.

Asserts

Java offers the assert keyword as part of the language, this has no equivalent in .NET. An assert evaluates a Boolean condition which, if not found to be true, throws an error:

assert speed < SPEED_OF_LIGHT;

Forgot to say, assertions can be disabled, which means, they are turned into no-ops.

Next Steps

I still have a couple of things to talk about, so be prepared for a future post!

As always, do let me know if you think I got anything wrong or you wish me to clarify something.

.NET Core Service Provider Gotchas and Less-Known Features

Introduction

In this post I’m going to talk about a few gotchas with the .NET Core’s built-in inversion of control (IoC) / service provider (SP)/dependency injection (DI) library. It is made available as the Microsoft.Extensions.DependencyInjection NuGet package.

I wrote another post some time ago, but this one supersedes it, in many ways.

Extension Methods

The single method exposed by the IServiceProvider interface, GetService, is not strongly typed. If you add a using statement for Microsoft.Extensions.DependencyInjection, you’ll get a few ones that are:

  • GetRequiredService<T>: tries to retrieve a service that is registered under the type of the generic template parameter and throws an exception if one cannot be found; if it is, it is cast to the template parameter;
  • GetService<T>: retrieves a service and casts it to the template parameter; if no service is found, null is returned;
  • GetServices<T>: returns all services registered as the template parameter type, cast appropriately.

Using a Different Service Provider

You are not forced to use the built-in service provider; you can use anyone you like, as long as it exposes an IServiceProvider implementation. You just need to return this implementation from the ConfigureServices method, which normally does not return anything:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
//return an implementation of IServiceProvider
}

Why would you want to do that, you may ask? Well, there are service providers out there that offer much more interesting features than Microsoft’s (for example, more lifetimes), and this has a reason: Microsoft kept his simple on purpose.

Multiple Registrations

You may not have realized that you can register any number of implementations for a given service, even with different lifetimes:

services.AddTransient<IService, ServiceA>();
services.AddScoped<IService, ServiceB>();

So, what happens when you ask for an implementation for IService? Well, you get the last one registered, in this case, ServiceB. However, you can ask for all the implementations, if you call GetServices<T>.

Registration Factories

You can specify how a service implementation is constructed when you register a service, and it can depend upon other services that are also registered in the service provider:

services.AddTransient<IService>(sp => new ServiceImpl(sp.GetRequiredService<IOtherService>));

Don’t worry about registration order: IOtherService will only be required once IService is retrieved.

Lifetime Dependencies

You cannot have a Singleton registration depend upon a Scoped service. This makes sense, if you think about it, as a singleton has a much longer lifetime than a scoped service.

Nested Scopes

You can create nested scopes at any time and retrieve services from them. If you are using the extension methods in the Microsoft.Extensions.DependencyInjection namespace, it’s as easy as this:

using (var scope = serviceProvider.CreateScope())
{
    var svc = scope.ServiceProvider.GetRequiredService<IService>();
}

The CreateScope method comes from the IServiceScopeFactory implementation that is registered automatically by the dependency injection implementation. See next for implications of this.

Why is this needed? Because of lifetime dependencies: using this approach you can instantiate a service marked as a singleton that takes as a parameter a scoped one, inside a scope.

Dispose Pattern

All services instantiated using the Scoped or Transient lifetimes that implement the IDisposable interface will have their Dispose methods called at the end of the request – or the nested scope (when it is disposed). The root service provider is only disposed with the app itself.

Scope Validation

The built-in service provider validates the registrations so that a singleton does not depend on a scoped registration. This has the effect of preventing retrieving services in the Configure method, through IApplicationBuilder.ApplicationServices, that are not transient or singletons.

If, however, you think you know what you’re doing, you can bypass this validation:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
//add services
return services.BuildServiceProvider(validateScopes: false);
}

As I said before, the other alternative is creating a scope and instantiating your singleton service inside the scope. This will always work.

Injecting Services

ASP.NET Core only supports constructor:

public HomeController(IService svc)
{
}

and parameter:

public IActionResult Index([FromServices] IService svc)
{
}

inheritance, but not property, in controllers and Razor Pages. You can achieve that through actions or conventions. Another option is to use the Service Locator pattern.

Service Locator

You can retrieve any registered services from HttpContext.RequestServices, so whenever you have a reference to an HttpContext, you’re good. From the Configure method, you can also retrieve services from IApplicationBuilder.ApplicationServices, but not scoped ones (read the previous topics). However, it is generally accepted that you should prefer constructor or parameter injection over the Service Locator approach.

Conclusion

Although the service provider that comes with .NET Core is OK for most scenarios, it is clearly insufficient for a number of others. These include:

  • Other lifetimes, such as, per resolve-context, per thread, etc;
  • Property injection;
  • Lazy<T> support;
  • Named registrations;
  • Automatic discovery and configuration of services;
  • Child containers.

You should consider a more featured DI library, and there are many out there, if you need any of these.

Integrating Managed Extensibility Framework with the .NET Service Provider

Introduction

It seems I’m in the mood for Managed Extensibility Framework: second post in a week about it! This time, I’m going to talk about how we can integrate it with the .NET Core’s service provider/dependency injection (DI) library (Microsoft.Extensions.DependencyInjection).

Mind you, this will apply to both ASP.NET Core and .NET Core console apps.

Locating Services

We’ve seen before how we can find all types that match a given interface:

public static class ContainerConfigurationExtensions     {         public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)         {             return WithAssembliesInPath(configuration, path, null, searchOption);         }         public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly)         {             var assemblyFiles = Directory                 .GetFiles(path, "*.dll", searchOption);             var assemblies = assemblyFiles                 .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);             configuration = configuration.WithAssemblies(assemblies, conventions);             return configuration;         }     }

Service Registration

The next step is picking up all of the found types and registering them with the DI:

public static class ServiceCollectionExtensions     {         public static IServiceCollection AddFromAssembliesInPath<T>(this IServiceCollection services, ServiceLifetime lifetime, string path = null) where T : class         {             var factory = new ExportFactory<T, object>(() => new Tuple<T, Action>(Activator.CreateInstance<T>(), () => { }), new object());             var conventions = new ConventionBuilder();             var builder = conventions                 .ForTypesDerivedFrom<T>()                 .Export<T>();             if (lifetime == ServiceLifetime.Singleton)             {                 builder = builder.Shared();             }             path = path ?? AppContext.BaseDirectory;             var configuration = new ContainerConfiguration()                 .WithAssembliesInPath(path, conventions);             using (var container = configuration.CreateContainer())             {                 var svcs = container.GetExports<Lazy<T>>();                 foreach (var svc in svcs)                 {                     services.Add(new ServiceDescriptor(typeof(T), sp => svc.Value, lifetime));                 }             }             return services;         }         public static IServiceCollection AddSingletonFromAssembliesInPath<T>(this IServiceCollection services, string path = null) where T : class         {             return AddFromAssembliesInPath<T>(services, ServiceLifetime.Singleton, path);         }         public static IServiceCollection AddScopedFromAssembliesInPath<T>(this IServiceCollection services, string path = null) where T : class         {             return AddFromAssembliesInPath<T>(services, ServiceLifetime.Scoped, path);         }         public static IServiceCollection AddTransientFromAssembliesInPath<T>(this IServiceCollection services, string path = null) where T : class         {             return AddFromAssembliesInPath<T>(services, ServiceLifetime.Transient, path);         }     }

The AddFromAssembliesInPath extension method is what does all the work; it leverages the previous WithAssembliesInPath method to locate all types that match a given interface, in the assemblies inside a specific folder (which can be the current one). AddSingletonFromAssembliesInPath, AddScopedFromAssembliesInPath and AddTransientFromAssembliesInPath are merely here to make your life a (little bit) easier. Although MEF only supports singletons (Shared) and transient (Non-shared) lifetimes, with this approach

Notice how MEF let’s us resolve Lazy<T> instances besides T. This is pretty cool, as we can delay object instantiation to a later stage, when the object is actually needed. A word of caution: the instantiation will actually be done by MEF, not by the .NET Core DI, so you won’t have constructor injection.

Putting it all Together

So, armed with these two extension methods, we can add this to the ConfigureServices method of your ASP.NET Core app (or wherever you populate your service provider):

services.AddTransientFromAssembliesInPath<IPlugin>();

Here IPlugin is just some interface, nothing to do with the one described in the previous post. After this, you should be able to inject all of the actual implementations:

public class HomeController : Controller

{

public HomeController(IEnumerable<IPlugin> plugins) { … }

}