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


我很想知道是否有其他人遇到过同样的问题...... 我在项目的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> 哪一个 应该 是 IEnumerable<T>,我尝试对它进行操作,Visual Studio给我没有intellisense属性,除了那些继承通过 object

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

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

'System.Collections.Generic.List<MyNameSpace.MyClass>' does not contain a definition for 'First'

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

我想这个错误正在发生,因为编译器无法构建通用模板,因为它不知道Query正在返回 IEnumerable<T> 因为它正在DLR中执行?我很想听到有人解释这个知识渊博的人。我基本上找到了两种方法来解决它:

  • 投了 dynamic param to a 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;
    }
}

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

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

综上所述:

我是新手,通过DLR的qwerks,在搞乱动态+泛型时,似乎需要记住一些注意事项......?

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


2216
2018-06-13 02:44


起源

可能重复 c#中的扩展方法和动态对象 - Kirk Woll
这很棒,但请注意,对于自我回答的问题,理想的做法是分别发布问题和答案。 - McGarnagle
@dbaseman谢谢。我写了一个单独的答案,但显然我没有足够的分数来“自我回答”,直到8个小时过去了:P - Leland Richardson
哈......太糟糕了。有人贬低你的帖子,这是一个非常荒谬的事情,这是我在SO上看到的更好的帖子之一。 - McGarnagle
在过去的几天里,我遇到了像你这样的问题 回答了问题。希望能帮助到你。 - Jone Polvora


答案:


正如所建议的那样,我会尽力而为 回答 我在实际答案中的问题......(现在已经8小时了)

我对这个问题的理解是这样的:

  • 如中描述的那样 引用的问题,动态类型没有可用的扩展方法,但扩展方法可以正常使用(如  方法),就像他们没有 this 关键词...

例如:

dynamic list = someListObject;

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

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

正如Jon Skeet指出的那样 这个答案 这完全是设计和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(); 

10
2018-06-13 14:51



只有在转换为实际类型(例如ExtendedObject)时,它似乎才有效。转换为对象似乎不适用于此。 - Mary Ellen Bench