Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
434 views
in Technique[技术] by (71.8m points)

c# - Factory Pattern with Open Generics

In ASP.NET Core, one of the things you can do with Microsoft's dependency injection framework is bind "open generics" (generic types unbound to a concrete type) like so:

public void ConfigureServices(IServiceCollection services) {
    services.AddSingleton(typeof(IRepository<>), typeof(Repository<>))
}

You can also employ the factory pattern to hydrate dependencies. Here's a contrived example:

public interface IFactory<out T> {
    T Provide();
}

public void ConfigureServices(IServiceCollection services) {
    services.AddTransient(typeof(IFactory<>), typeof(Factory<>));

    services.AddSingleton(
        typeof(IRepository<Foo>), 
        p => p.GetRequiredService<IFactory<IRepository<Foo>>().Provide()
    ); 
}

However, I have not been able to figure out how to combine the two concepts together. It seems like it would start with something like this, but I need the concrete type that is being used to hydrate an instance of IRepository<>.

public void ConfigureServices(IServiceCollection services) {
    services.AddTransient(typeof(IFactory<>), typeof(Factory<>));

    services.AddSingleton(
        typeof(IRepository<>), 
        provider => {
            // Say the IServiceProvider is trying to hydrate 
            // IRepository<Foo> when this lambda is invoked. 
            // In that case, I need access to a System.Type 
            // object which is IRepository<Foo>. 
            // i.e.: repositoryType = typeof(IRepository<Foo>);

            // If I had that, I could snag the generic argument
            // from IRepository<Foo> and hydrate the factory, like so:

            var modelType = repositoryType.GetGenericArguments()[0];
            var factoryType = typeof(IFactory<IRepository<>>).MakeGenericType(modelType);
            var factory = (IFactory<object>)p.GetRequiredService(factoryType);

            return factory.Provide();
        }           
    ); 
}

If I try to use the Func<IServiceProvider, object> functor with an open generic, I get this ArgumentException with the message Open generic service type 'IRepository<T>' requires registering an open generic implementation type. from the dotnet CLI. It doesn't even get to the lambda.

Is this type of binding possible with Microsoft's dependency injection framework?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The net.core dependency does not allow you to provide a factory method when registering an open generic type, but you can work around this by providing a type that will implement the requested interface, but internally it will act as a factory. A factory in disguise:

services.AddSingleton(typeof(IMongoCollection<>), typeof(MongoCollectionFactory<>)); //this is the important part
services.AddSingleton(typeof(IRepository<>), typeof(Repository<>))

public class Repository : IRepository {
    private readonly IMongoCollection _collection;
    public Repository(IMongoCollection collection)
    {
        _collection = collection;
    }

    // .. rest of the implementation
}

//and this is important as well
public class MongoCollectionFactory<T> : IMongoCollection<T> {
    private readonly _collection;

    public RepositoryFactoryAdapter(IMongoDatabase database) {
        // do the factory work here
        _collection = database.GetCollection<T>(typeof(T).Name.ToLowerInvariant())
    }

    public T Find(string id) 
    {
        return collection.Find(id);
    }   
    // ... etc. all the remaining members of the IMongoCollection<T>, 
    // you can generate this easily with ReSharper, by running 
    // delegate implementation to a new field refactoring
}

When the container resolves the MongoCollectionFactory it will know what type T is and will create the collection correctly. Then we take that created collection save it internally, and delegate all calls to it. ( We are mimicking this=factory.Create() which is not allowed in csharp. :))

Update: As pointed out by Kristian Hellang the same pattern is used by ASP.NET Logging

public class Logger<T> : ILogger<T>
{
    private readonly ILogger _logger;

    public Logger(ILoggerFactory factory)
    {
        _logger = factory.CreateLogger(TypeNameHelper.GetTypeDisplayName(typeof(T)));
    }

    void ILogger.Log<TState>(...)
    {
        _logger.Log(logLevel, eventId, state, exception, formatter);
    }
}

https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Abstractions/LoggerOfT.cs#L29

original discussion here:

https://twitter.com/khellang/status/839120286222012416


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...