更新:创建简单解决方案
正如埃里克在下面所说,我有点非常高兴,但它仍然无法解决我的问题。我已经找到了一个更好的解决方案,用于从我的Core程序集中删除MassTransit依赖项。
我在下面详细介绍了答案。
原始问题
有没有办法对泛型参数设置约束,说明它必须是具体实现的类型?
现在我正在使用进行反射的单元测试来确保所有实现都正确形成,但我很乐意让编译器失败。
示例:
抽象基础
// this abstract class extends another generic type that needs to know
// the type of the concrete implementation "TConsumer"
public abstract class ConsumerBase<TMessage,TConsumer> :
Consumes<AssignedMessage<TMessage,TConsumer>>.All
where TMessage : IMessage
where TConsumer : {{The class that's implementing me}}
{
}
应该成功
// this class passes it's own type as a generic arg to it's base
// and it works great!
public class TestConsumer : ConsumerBase<TestMessage, TestConsumer>
{ }
应该失败
// this class passes a different type as a generic arg to its base
// and should FAIL!!! but how?
public class FailConsumer : ConsumerBase<TestMessage, TestConsumer>
{ }
更新:进一步说明
进一步解释为什么我要这样做......
我正在尝试在MassTransit上创建一个抽象(说起来容易做起来难)。公共交通需要消费者实施Consume&lt; TMessage&gt ;.All。
就我而言,TMessage是AssignedMessage&lt; TRawMessage,TAssignee&gt;。
需要使用AssignedMessage&lt; TRawMessage,TAssignee&gt;实现MassTransit的通用Consumes&lt; TMessage&gt; .All。意味着我需要传递通用TAssignee参数。
话虽如此,它仍然没有完全抽象出MassTransit。它为我提供了一个基于公共交通的基类,它隐藏了我正在实现一个MassTransit接口的事实,但它仍然是一个MassTransit依赖。
答案 0 :(得分:3)
正如李正确指出的那样,这个概念在C#类型系统中不存在。你可以近距离接触,但不能完全到达那里。我关于这个主题的文章在这里:
http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx
我建议您使用Genericity Happiness Disease,这种情况会导致开发人员尝试在泛型类型和约束的复杂相互作用中捕获所有业务逻辑,而不管用户对此类用户的混淆程度如何。类型。
通用类型系统旨在解决问题“我有一个苹果列表和恐龙列表以及价格列表;我希望所有这些列表共享相同的实现”。你的类型:
public abstract class ConsumerBase<TMessage,TConsumer> :
Consumes<AssignedMessage<TMessage,TConsumer>>
滥用泛型类型系统。假设你问某人“消息和消费者的消费者是什么?”任何没有遭受通用性快乐疾病困扰的人都会说“消费者的消息和消费者是消费消息和消费者的消息的消费品吗?”
我的建议是,大量简化您的系统。你试图在类型系统中捕获太多的东西。
答案 1 :(得分:2)
我认为你能得到的最接近的是:
where TConsumer : ConsumerBase<TMessage, TConsumer>
重复出现的模板很快就会变得难以处理,但仍有可能被绕过,例如。
public class LyingConsumer<TestMessage, TestConsumer> { ... }
另一种方法是更改您的设计,以便您可以将通用参数放在类本身之外,例如
public void SomeMethod<TConsumer, TMessage>(TConsumer consumer, TMessage message)
where TConsumer : IConsumer<TMessage>
where TMessage : IMessage
答案 2 :(得分:2)
我设法以相当简单的方式解决了MassTransit依赖问题,而没有过于复杂的泛型或运行时发出。
在下面的示例中,我们使用以下Nuget包: MassTransit,MassTransit.RabbitMq,MassTransit.Ninject
我们域名的简单消费者(汇编: OurDomain.Core )
我们的核心程序集没有对MassTransit的引用或依赖。此外,任何其他想要实现消费者的程序集也不需要引用MassTransit。
public interface IConsume<T> where T : MessageBase
{
int RetryAllotment { get; }
void Consume(T message);
}
public class ExampleConsumer : IConsume<ExampleMessage>
{
public int RetryAllotment { get { return 3; } }
public void Consume(ExampleMessage message){...}
}
内部MassTransitConsumer(程序集: OurDomain.Messaging )
Messaging程序集是唯一引用MassTransit的程序集。它还引用了核心程序集
此类的目的是简单地为我们的域用户提供一个包装器,用于连接MassTransit订阅
internal class MassTransitConsumer<TMessage, TConsumer> : Consumes<TMessage>.Context
where TMessage : MessageBase
where TConsumer : IConsume<TMessage>
{
private readonly TConsumer _consumer;
public MassTransitConsumer(TConsumer consumer)
{
_consumer = consumer;
}
public void Consume(IConsumeContext<TMessage> ctx)
{
try
{
_consumer.Consume(ctx.Message);
}
catch
{
if (ctx.RetryCount >= _consumer.RetryAllotment)
throw;
ctx.RetryLater();
}
}
}
配置程序(程序集: OurDomain.Messaging )
特别注意 GetMassTransitConsumer 方法
public class MassTransitConfigurator
{
private readonly string _rabbitMqConnection;
private readonly Queue _queue;
private readonly IKernel _kernel;
private readonly IEnumerable<Type> _consumers;
private readonly IEnumerable<Type> _massTransitConsumers;
public enum Queue
{
Worker,
WorkerProducer,
}
public MassTransitConfigurator(string rabbitMqConnection, Queue queue, IKernel kernel,
IEnumerable<Type> consumers)
{
_rabbitMqConnection = rabbitMqConnection;
_queue = queue;
_kernel = kernel;
_consumers = consumers;
if (_queue == Queue.Worker)
_massTransitConsumers = _consumers.Select(GetMassTransitConsumer);
}
private static Type GetMassTransitConsumer(Type domainConsumer)
{
var interfaceType = domainConsumer.GetInterface(typeof (IConsume<>).Name);
if (interfaceType == null)
throw new ArgumentException("All consumers must implement IConsume<>.", "domainConsumer");
var messageType = interfaceType.GetGenericArguments().First();
var massTransitConsumer = typeof (MassTransitConsumer<,>).MakeGenericType(messageType, domainConsumer);
return massTransitConsumer;
}
public void Configure()
{
if (_queue == Queue.Worker)
{
foreach (var consumer in _consumers)
_kernel.Bind(consumer).ToSelf();
foreach (var massTransitConsumer in _massTransitConsumers)
_kernel.Bind(massTransitConsumer).ToSelf();
}
var massTransitServiceBus = ServiceBusFactory.New(ConfigureMassTransit);
var ourServiceBus = new MassTransitServiceBus(massTransitServiceBus);
_kernel.Bind<OurDomain.IServiceBus>().ToConstant(ourServiceBus);
}
private void ConfigureMassTransit(ServiceBusConfigurator config)
{
config.UseRabbitMq();
var queueConnection = _rabbitMqConnection + "/" + _queue;
config.ReceiveFrom(queueConnection);
if (_queue == Queue.Worker)
{
foreach (var massTransitConsumer in _massTransitConsumers)
{
var consumerType = massTransitConsumer;
config.Subscribe(s => s.Consumer(consumerType, t => _kernel.Get(t)));
}
}
}
}
订阅示例程序(程序集: OurDomain.Services.Worker )
工作者程序集引用了核心程序集以及我们的IConsume实现恰好定义的任何程序集。
但是,它没有参考MassTransit。相反,它引用我们的Messaging程序集,它在内部连接MassTransit。
internal class Program
{
private static void Main(string[] args)
{
var kernel = new StandardKernel();
var rabbitMqConnection = "rabbitmq://localhost";
var consumers = new[] { typeof(ExampleConsumer) };
var configurator = new MassTransitConfigurator(
rabbitMqConnection,
MassTransitConfigurator.Queue.Worker,
kernel,
consumers);
configurator.Configure();
}
}