所以,我已经阅读了有关C#中协方差和协方差的很多知识,并以为我理解了,但是此代码无法正常工作,我不确定为什么。
public class EventService
{
private readonly Dictionary<Type, ImmutableList<Func<DomainEvent, Task>>> _listeners = new Dictionary<Type, ImmutableList<Func<DomainEvent, Task>>>();
public async Task Fire(DomainEvent domainEvent)
{
var listeners = _listeners.GetValueOrDefault(domainEvent.GetType(), ImmutableList<Func<DomainEvent, Task>>.Empty);
foreach (var listener in listeners!)
{
await listener.Invoke(domainEvent);
}
}
public void AddListener<T>(Func<T, Task> func) where T: DomainEvent
{
var listeners = _listeners.GetValueOrDefault(typeof(T), ImmutableList<Func<DomainEvent, Task>>.Empty);
_listeners[typeof(T)] = listeners.Add(func); // This line fails with: 'Argument type 'System.Func<T,System.Threading.Tasks.Task>' is not assignable to parameter type 'System.Func<Domain.Core.DomainEvent,System.Threading.Tasks.Task>'
}
}
public abstract class DomainEvent
{
}
我以为where T: DomainEvent
应该允许它工作,因为一切都是领域事件,但它不是那样的。到目前为止,我还无法弄清楚我做错了什么,以及我的误解在哪里。
T
中的[Func<T, Task>
是逆变的,定义如下:
public delegate TResult Func<in T,out TResult>(T arg);
在Microsoft Docs - Func<T,TResult>
Delegate处,因此它必须比Func<T,TResult>
少。但是有可能T
比T
派生更多。可以说您具有以下派生类型:
DomainEvent
如果将public class FooDomainEvent : DomainEvent { }
public class BarDomainEvent : DomainEvent { }
替换为T
,它将可以正常工作:
object
如果将public void AddListener<T>(Func<object, Task> func) where T : DomainEvent
替换为T
,它也将起作用,如下所示:
DomainEvent
但是,由于将public void AddListener<T>(Func<DomainEvent, Task> func) where T : DomainEvent
替换为T
会比较麻烦,因为它是派生的:
FooDomainEvent
这里是使它工作的方法,首先是示例public void AddListener<T>(Func<FooDomainEvent, Task> func) where T : DomainEvent
:
Program.cs
然后修改您的代码以将using System;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static async Task Main(string[] args)
{
var evtSvc = new EventService();
evtSvc.AddListener<FooDomainEvent>(
FooDomainEvent =>
{
Console.WriteLine("Foo Called");
return Task.CompletedTask;
});
evtSvc.AddListener<BarDomainEvent>(
BarDomainEvent =>
{
Console.WriteLine("Bar Called");
return Task.CompletedTask;
});
await evtSvc.Fire(new FooDomainEvent());
await evtSvc.Fire(new BarDomainEvent());
Console.ReadLine();
}
}
}
替换为T
:
DomainEvent