无法理解通用约束为何不起作用

问题描述 投票:0回答:1

所以,我已经阅读了有关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应该允许它工作,因为一切都是领域事件,但它不是那样的。到目前为止,我还无法弄清楚我做错了什么,以及我的误解在哪里。

c# generics covariance
1个回答
0
投票

T中的[Func<T, Task>是逆变的,定义如下:

public delegate TResult Func<in T,out TResult>(T arg);

Microsoft Docs - Func<T,TResult> Delegate处,因此它必须比Func<T,TResult>少。但是有可能TT派生更多。可以说您具有以下派生类型:

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
© www.soinside.com 2019 - 2024. All rights reserved.