与 C# 中的泛型类型有些混淆

some confusion with generic types in c#

我正在尝试使用 cqs 并尝试在 class 库中实现它(因此没有 IOC、IServiceProvider 等)。这是我写的一些代码:

public interface IQuery<TResult>
{
}

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

public class Query : IQuery<bool>
{
    public int Value { get; set; }
}

public class QueryHandler : IQueryHandler<Query, bool>
{
    public bool Handle(Query query)
    {
        return query.Value > 0;
    }
}

public class Dispatcher
{
    private readonly Dictionary<Type, object> handlers = new Dictionary<Type, object>();

    public Dispatcher()
    {
        handlers.Add(typeof(Query), new QueryHandler());
    }

    public T Dispatch<T>(IQuery<T> query)
    {
        IQueryHandler<IQuery<T>, T> queryHandler;

        if (!this.handlers.TryGetValue(query.GetType(), out object handler) ||
            ((queryHandler = handler as IQueryHandler<IQuery<T>, T>) == null))
        {
            throw new Exception();
        }

        return queryHandler.Handle(query);
    }
}

这就是我调用代码的方式:

Query query = new Query();
Dispatcher dispatcher = new Dispatcher();
var result = dispatcher.Dispatch(query);

但问题是在调度器内部,我不知道为什么变量处理程序不能转换为IQueryHandler<IQuery<T>,T>。这是一些额外的数据:

PS:我知道如何让它工作(动态),但我想了解为什么这段代码不起作用。

这是一个协方差问题。 handler 的真实类型是 QueryHandler,所以它是一个 IQueryHandler<Query, bool>。当然 Query 是一个 IQuery<bool>,但这就是协方差的关键。

这就像尝试将 List<String> 分配给类型为 List<Object> 的变量。

存在一个 out 关键字,允许您按预期在 IQueryHandler 界面上使用协方差。

有关详细信息,请参阅out

编辑:

正如 , you cannot use out on TQuery because it is used as input parameter. The correct solution is to avoid the dependecy of QueryHandler on Query. 所指出的那样,它很好地展示了它是如何完成的。

此代码不起作用,因为 IQueryHandlerTQuery 泛型参数上是不变的。 TQuery 需要协变才能将 handler 转换为 IQueryHandler<IQuery<T>, T>,但这是不可能的,我稍后会解释。但是,您可以使 TQuery 逆变,这允许您将 handler 转换为 IQueryHandler<ASubclassOfQuery, T>TResult 虽然可以是协变的。这是执行此操作的代码:

public interface IQueryHandler<in TQuery, out TResult> where TQuery : IQuery<TResult>

有关一般方差的详细信息,请参阅 this page

至于为什么handler不是IQueryHandler<IQuery<T>, T>,我们先假设,也就是说这段代码可以编译:

IQueryHandler<IQuery<T>, T> q = handler;
q.Handle(new MyQuery<T>());

其中 MyQuery 的定义如下:

class MyQuery<T> : IQuery<T> {}

但是,handler 是运行时类型 QueryHandlerQueryHandler.Handle 只处理 Query 个对象,不处理 MyQuery<T> 个对象!我们有一个矛盾,因此我们假设 handler 是一个 IQueryHandler<IQuery<T>, T> 一定是错误的。

你的下一行没有意义:

handler as IQueryHandler<T, T>

因为第一个和第二个类型参数永远不会与 Query 相同,而 Result 将始终是不同的类型。

您需要一些机制来在 Dispatcher 中提供第二个类型参数,一种方法是:

public class Dispatcher<TResult> 
{
    private readonly Dictionary<Type, object> handlers = new Dictionary<Type, object>();

    public Dispatcher()
    {
        handlers.Add(typeof(Query), new QueryHandler());
    }

    public TResult Dispatch<TQuery>(TQuery query) where TQuery : IQuery<TResult>
    {
        IQueryHandler<TQuery, TResult> queryHandler;

        if (!this.handlers.TryGetValue(query.GetType(), out object handler) ||
            ((queryHandler = handler as IQueryHandler<TQuery, TResult>) == null))
        {
            throw new Exception();
        }

        return queryHandler.Handle(query);
    }
}

它可以这样称呼:

Query query = new Query();
Dispatcher<bool> dispatcher = new Dispatcher<bool>();
var result = dispatcher.Dispatch(query);

另一种方法是在 Dispatch 方法中采用第二种类型参数,但我认为第一种方法更好:

public class Dispatcher<TQuery, TResult> where TQuery : IQuery<TResult>, new()
{
    private readonly Dictionary<Type, object> handlers = new Dictionary<Type, object>();

    public Dispatcher()
    {
        handlers.Add(typeof(Query), new QueryHandler());
    }

    public TResult Dispatch()
    {
        TQuery query = new TQuery();
        IQueryHandler<TQuery, TResult> queryHandler;

        if (!this.handlers.TryGetValue(query.GetType(), out object handler) ||
            ((queryHandler = handler as IQueryHandler<TQuery, TResult>) == null))
        {
            throw new Exception();
        }

        return queryHandler.Handle(query);
    }
}

调用方将如下所示:

Dispatcher<Query, bool> dispatcher = new Dispatcher<Query,bool>();
var result = dispatcher.Dispatch();

这是避免协方差问题的另一种方法:

public interface IQuery<TResult>
{
    TResult Value { get; set; }
}

public interface IQueryHandler<TResult>
{
    TResult Handle<TQuery>(TQuery query) where TQuery : IQuery<TResult>;
}

public class Query : IQuery<bool>
{
    public bool Value { get; set; }
}

public class QueryHandler : IQueryHandler<bool>
{
    public bool Handle<TQuery>(TQuery query) where TQuery : IQuery<bool>
    {
        return query.Value;
    }
}

public class Dispatcher
{
    private readonly Dictionary<Type, object> handlers = new Dictionary<Type, object>();

    public Dispatcher()
    {
        handlers.Add(typeof(Query), new QueryHandler());
    }

    public T Dispatch<T>(IQuery<T> query)
    {
        if (handlers.ContainsKey(query.GetType()))
        {
            var queryHandler = (IQueryHandler<T>)handlers[query.GetType()];
            return queryHandler.Handle(query);
        }

        throw new NotSupportedException();
    }
}

示例:

var queryHandler = new QueryHandler();
var query = new Query();
query.Value = true;
var dispatcher = new Dispatcher();
dispatcher.Dispatch(query);
>> True