可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Suppose I have an interface for a service:
public interface IFooService
{
void DoSomething();
}
And a concrete implementation of that service that is a generic:
public class FooService<TRequestingClass> : IFooService
{
public virtual void DoSomething() { }
}
And I have some other class that needs an instance of IFooService:
public class Bar
{
private IFooService _fooService;
public Bar(IFooService fooService)
{
this._fooService = fooService;
}
}
I need to wire up my IoC container such that when Bar gets created, it gets passed a constructor argument of FooService<Bar>. There are many other classes just like Bar. Each might also need an instance of FooService<TRequestingClass> passed to them where TRequestingClass is the type of the class that needs the instance of IFooService. I don't need to expose this quirkiness to the consumers of IFooService. All they should care about is that they can call the methods of the IFooService they were passed. They should not need to know that the concrete implementation of IFooService they were passed needed anything special to be constructed.
An acceptable alternatative to FooService<T> would to be a non-generic class that has a string argument in its constructur that contains the name of the class it is being created for. i.e:
public class FooService : IFooService
{
public FooService(string requestingClassName) { }
}
How can I wire up my IoC container to construct a dependency this way?
If you are confused why I would want such a wierd structure, consider how log4net works best when you get an ILog that gets created with log4net.LogManager.GetLogger(typeof(SomeClass)). I don't want to litter my code with references to log4net, so I'd like to write a simple ILogger interface, and implement it with something like this:
public class GenericLogger<T> : ILogger
{
private readonly ILog log;
public GenericLogger()
{
this.log = log4net.LogManager.GetLogger(typeof(T));
}
public void Debug(object message)
{
this.log.Debug(message);
}
/* .... etc .... */
}
回答1:
The easiest way would be to create an ILogger<T>
interface:
public class ILogger<T> : ILogger { }
public class GenericLogger<T> : ILogger<T> { ... }
Then rely on generic type inference to get the correct type. For example, in Ninject, the following binding is all you'd need:
Bind(typeof(ILogger<>)).To(typeof(GenericLogger<>));
Then your consuming types would look like this:
public class FooService : IFooService {
public FooService(ILogger<FooService> logger) { ... }
}
If you're adamantly against the ILogger<T>
interface, you could do something more creative, like a custom provider, which reads the IContext
to determine the parent type.
public class GenericLogger : ILogger {
public class GenericLogger(Type type) { ... }
}
public class LoggerProvider : Provider<ILogger> {
public override ILogger CreateInstance(IContext context) {
return new GenericLogger(context.Target.Member.ReflectedType);
}
}
Then consuming types would work like this:
public class FooService : IFooService {
public FooService(ILogger logger) { ... }
}
回答2:
If i'm not misunderstanding you, why not simply have your GericLogger Constructor take a parameter that is the type of the object. Then do this:
ILog = kernel.Get<ILog>(ParameterList);
I haven't read up completely yet on Ninject Parameter Lists, but it seems to be a way to inject a parameter type using an IParameterList.
EDIT:
Looks like this would work like this:
ILog = kernel.Get<ILog>(new ConstructorArgument[] {
new ConstructorArgument("ClassName", this.GetType().Name)
})
Then you have your ILog
class GenericLogger : Ilog
{
GenericLogger(string ClassName) {};
}
I didn't test this, just what seems to be from the Ninject source (i'm looking at a recent Ninject2 tree)
EDIT:
You want to pass ConstructorArgument, not Parameter. Updated to reflect.
This code Prints: "Called From Init"
class Init {
public void Run() {
StandardKernel kernel = new StandardKernel( new MyLoader() );
kernel.Get<Tester>( new ConstructorArgument[] {
new ConstructorArgument( "ClassName",
this.GetType().Name
)
} );
}
}
public class Tester {
public Tester(string ClassName) {
Console.WriteLine("Called From {0}", ClassName);
}
}
EDIT:
Another method which might be useful is to use The Bind().To().WithConstructorArgument() binding, which could be useful when used with either self-binding or with a .WhenInjectedInto() condition.
回答3:
To elaborate on the custom provider idea, here's a simple way to do it...
internal class LogModule : StandardModule
{
private class log4netILogProvider : SimpleProvider<log4net.ILog>
{
protected override ILog CreateInstance(IContext context)
{
return LogManager.GetLogger(context.Instance.GetType());
}
}
public override void Load()
{
Bind<log4net.ILog>().ToProvider(new log4netILogProvider());
}
}
Then in classes that are injected:
[Inject]
public ILog logger { get; set; }