无法使用动态参数和泛型调用扩展方法

时间:2012-06-13 02:44:39

标签: c# generics dynamic extension-methods dapper

我很想知道是否有其他人遇到过同样的问题...... 我在项目的ORM上使用Dapper,并在IDbConnection接口之外创建了一些我自己的扩展方法,以简化代码,我遇到了(我发现的)令人费解的错误。

我将介绍我经历的过程。

首先,我在一个名为DbExtensions的静态类中为我的项目添加了一个扩展方法,如下所示:

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = cnn.Query<T>(sql, param as object, transaction, buffered, commandTimeout, commandType).First();
        return ret;
    }
}

这将使用以下描述创建编译错误:

'System.Data.IDbConnection' has no applicable method named 'Query' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.

这很好,错误实际上很有帮助,因为它甚至告诉我如何解决它。所以我接着尝试:

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = SqlMapper.Query<T>(cnn, sql, param, transaction, buffered, commandTimeout, commandType).First();
        return ret;
    }
}

它正确编译。虽然有些奇怪的事情发生了。在Visual Studio中,如果我获取 应该SqlMapper.Query<T>的{​​{1}}的返回值,并且我尝试对其进行操作,则Visual Studio不会给我任何智能感知属性通过IEnumerable<T>继承。

以为我只是在做一些智能感知不够聪明的事情,我继续我的快乐方式......直到我真的尝试运行代码。

当我尝试运行它时,它会在我呼叫object的地方绊倒,但出现以下错误:

.First()

现在这个错误,我觉得很有意思......在敲了一下头之后,我意识到第一个论点是抱怨动态打字......

我想这个错误正在发生,因为编译器无法构建通用模板,因为它不知道Query正在返回'System.Collections.Generic.List<MyNameSpace.MyClass>' does not contain a definition for 'First',因为它正在DLR中执行?我很想听到有人解释这个知识渊博的人。我基本上找到了两种解决方法:

  • IEnumerable<T>参数投射到dynamic
  • 将返回的值投射到object

IEnumerable<T>

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = SqlMapper.Query<T>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType).First();
        return ret;
    }
}

摘要:

我不熟悉DLR的qwerks,在搞乱动态+泛型时,似乎有一些需要注意的事项......?

我知道这不是一个问题本身,但是当我真正开始写这篇文章时,我不知道发生了什么,我在这个过程中想出来了!我认为它可能会帮助其他有类似问题的人...

1 个答案:

答案 0 :(得分:10)

根据建议,我会在实际答案中尝试回答我的问题......(现在已经8小时了)

我对这个问题的理解是:

  • referenced question所述,动态类型没有可用的扩展方法,但扩展方法可以正常使用(作为实例方法),就像它们没有this关键字...

例如:

dynamic list = someListObject;

var item = list.First(); //this will not compile

var item = Enumerable.First(list);  //this will compile

正如Jon Skeet在this answer中所指出的,这完全是设计和DLR实现的一部分 - 如果任何调用具有动态参数,则返回类型将被视为动态。

  • 出于类似的原因,在扩展方法中使用动态变量有点不稳定......

public static Enumerable<T> ExtensionMethod(this ExtendedObject p1, dynamic p2) {
    //Do Stuff
}

dynamic y = something;
var x = new ExtendedObject();

//this works
var returnedEnumerable = x.ExtensionMethod(y); 

//this doesn't work
var returnedValue = x.ExtensionMethod(y).SomeEnumerableExtensionMethodLikeFirst() 

要使上述示例有效,您可以执行以下操作之一:

//cast dynamic as object
var returnedValue = x.ExtensionMethod(y as object).First(); 
//cast returned object
var returnedValue = ((IEnumerable<KnownType>)x.ExtensionMethod(y)).First();