与 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
. 所指出的那样,它很好地展示了它是如何完成的。
此代码不起作用,因为 IQueryHandler
在 TQuery
泛型参数上是不变的。 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
是运行时类型 QueryHandler
。 QueryHandler.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
我正在尝试使用 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
编辑:
正如 out
on TQuery
because it is used as input parameter. The correct solution is to avoid the dependecy of QueryHandler
on Query
.
此代码不起作用,因为 IQueryHandler
在 TQuery
泛型参数上是不变的。 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
是运行时类型 QueryHandler
。 QueryHandler.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