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

标签 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> 的返回值应该IEnumerable<T> ,我尝试对其进行操作,Visual Studio 没有给我智能感知属性,除了那些通过 object 继承的属性.

认为我只是在做一些 intellisense 不够聪明无法弄清楚的事情,我继续我的快乐之路......直到我真正尝试运行代码。

当我尝试运行它时,它在我调用 .First() 的地方出错了出现以下错误:

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

现在这个错误,我觉得很有趣......在我的头上敲了一会儿之后,我意识到第一个参数是在提示动态类型......

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

  • 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;
    }
}

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,在使用动态 + 泛型时似乎需要牢记一些注意事项......?

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

最佳答案

按照建议,我会尝试用实际答案回答我的问题...(现在已经 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(); 

关于c# - 无法使用动态参数和泛型调用扩展方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11007573/

相关文章:

c# - 查找两个字符串之间的不同单词

generics - 了解 syb 样板文件消除

c# - 无法将 IEnumerable<T> 的类型隐式转换为 IEnumerable<T>

php - 使用 PHP 创建动态表

c - 为什么动态二维字符数组中的值被覆盖?

regex - 如何在运行时设置正则表达式数据注释正则表达式参数?

c# - 外语的语音转文本和文本转语音

c# - 动态创建 ASP.NET 内容页面

c# - 在 C# 中格式化大数

C# 通用事件