3

假设我有一个服务接口:

public interface IFooService
{
   void DoSomething();
}

该服务的具体实现是通用的:

public class FooService<TRequestingClass> : IFooService
{
   public virtual void DoSomething() { }
}

我还有其他一些需要 IFooService 实例的类:

public class Bar
{
   private IFooService _fooService;
   public Bar(IFooService fooService)
   {
      this._fooService = fooService;
   }
}

我需要连接我的 IoC 容器,这样当 Bar 被创建时,它会传递一个 FooService<Bar> 的构造函数参数。还有很多其他的类,就像 Bar 一样。每个可能还需要传递给他们的 FooService<TRequestingClass> 实例,其中 TRequestingClass 是需要 IFooService 实例的类的类型。我不需要将这种古怪暴露给 IFooService 的消费者。他们应该关心的是他们可以调用他们传递的 IFooService 的方法。他们不需要知道传递给他们的 IFooService 的具体实现需要构造任何特殊的东西。

FooService<T> 的可接受替代方案是非泛型类,其构造函数中包含字符串参数,其中包含为其创建的类的名称。IE:

public class FooService : IFooService
{
   public FooService(string requestingClassName) { }
}

如何连接我的 IoC 容器以这种方式构建依赖项?

如果您对我为什么想要这样一个奇怪的结构感到困惑,请考虑一下当您获得使用 log4net.LogManager.GetLogger(typeof(SomeClass)) 创建的 ILog 时 log4net 的最佳工作方式。我不想在我的代码中乱扔对 log4net 的引用,所以我想编写一个简单的 ILogger 接口,并用这样的东西来实现它:

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 ....  */
}
4

3 回答 3

6

最简单的方法是创建一个ILogger<T>接口:

public class ILogger<T> : ILogger { }
public class GenericLogger<T> : ILogger<T> { ... }

然后依靠泛型类型推断来获得正确的类型。例如,在 Ninject 中,您只需要以下绑定:

Bind(typeof(ILogger<>)).To(typeof(GenericLogger<>));

那么您的消费类型将如下所示:

public class FooService : IFooService {
  public FooService(ILogger<FooService> logger) { ... }
}

如果你坚决反对ILogger<T>接口,你可以做一些更有创意的事情,比如自定义提供程序,它读取IContext来确定父类型。

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);
  }
}

然后消费类型将像这样工作:

public class FooService : IFooService {
  public FooService(ILogger logger) { ... }
}
于 2009-04-05T18:19:00.137 回答
1

如果我没有误解你,为什么不简单地让你的 GericLogger 构造函数采用对象类型的参数。然后这样做:

ILog = kernel.Get<ILog>(ParameterList);

我还没有完全阅读 Ninject Parameter Lists,但这似乎是一种使用 IParameterList 注入参数类型的方法。

编辑:

看起来像这样工作:

ILog = kernel.Get<ILog>(new ConstructorArgument[] { 
    new ConstructorArgument("ClassName", this.GetType().Name) 
})

然后你有你的 ILog

class GenericLogger : Ilog
{
    GenericLogger(string ClassName) {};
}

我没有对此进行测试,只是似乎来自 Ninject 源(我正在查看最近的 Ninject2 树)

编辑:

您想传递 ConstructorArgument,而不是 Parameter。更新以反映。

此代码打印:“从初始化调用”

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);
    }
}

编辑:

另一种可能有用的方法是使用 Bind().To().WithConstructorArgument() 绑定,这在与自绑定或 .WhenInjectedInto() 条件一起使用时可能很有用。

于 2009-04-05T18:24:46.317 回答
0

要详细说明自定义提供程序的想法,这里有一个简单的方法......

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());
        }
    }

然后在注入的类中:

[Inject]
        public ILog logger { get; set; }    
于 2009-08-04T20:53:09.150 回答