【问题标题】:some confusion with generic types in c#与 C# 中的泛型类型有些混淆
【发布时间】:2019-09-13 06:54:06
【问题描述】:

我正在使用 cqs,我正在尝试在类库中实现它(因此没有 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);

但是问题是在dispatcher里面,我不知道为什么变量handler不能被强制转换为IQueryHandler&lt;IQuery&lt;T&gt;,T&gt;。这是一些额外的数据:

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

【问题讨论】:

  • 这是哪个版本的 C# 和运行时?它已经支持out 了吗?
  • .net 框架 4.7

标签: c# generics casting cqrs


【解决方案1】:

这是一个协方差问题。 handler的真实类型是QueryHandler,所以是IQueryHandler&lt;Query, bool&gt;。当然QueryIQuery&lt;bool&gt;,但这就是协方差的意义所在。

这就像试图将List&lt;String&gt; 分配给List&lt;Object&gt; 类型的变量。

存在一个out 关键字,可让您按预期使用IQueryHandler 接口上的协方差。

详情请见out

编辑:

正如Sweeper 所指出的,您不能在TQuery 上使用out,因为它被用作输入参数。正确的解决方案是避免QueryHandlerQuery 的依赖。 Isma 很好地展示了它是如何完成的。

【讨论】:

  • TQuery 可以是协变的吗?它用作输入参数。
  • 啊,不,你说得对,我想得还不够远。
【解决方案2】:

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

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

有关通用差异的更多信息,请参阅this page

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

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

MyQuery 的定义如下:

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

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

【讨论】:

  • 你能提供一个小提琴吗?我似乎无法让它工作。
  • @John 它不应该工作。通过添加inout,我是在建议使代码更灵活的方法,但是“handler不是IQueryHandler&lt;IQuery&lt;T&gt;, T&gt;”的问题无法解决。请参阅答案的后半部分了解原因。
  • 啊,我明白了。明白了。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    您的以下行没有意义:

    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();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-01
      • 2011-11-04
      • 2015-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多